mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v7 00/14] rv: Add task latency over budget RV monitor
@ 2026-10-11 19:39 wen.yang
  2026-10-11 19:39 ` [PATCH v7 01/14] rv: Force environment reset action on HA monitor start wen.yang
                   ` (13 more replies)
  0 siblings, 14 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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 later restarts in place, reusing the
same pool slot (see Patch 11).

The series opens with five patches that fix the framework on their own
and apply to the base independently of tlob; the rest add the
infrastructure tlob needs and then tlob itself.

Patches 1 and 2 fix the HA environment lifecycle and must stay in this
order.  Patch 1 stops storing the ENV_INVALID_VALUE sentinel: monitors
now reset their stored variables to a real clock anchor on start.  Patch
2 then clamps the timer remainder and rewrites the invariant check as
store + expire_ns >= time_ns so neither side underflows.  That rewrite
overflows when the stored value is still the sentinel, so it has to
follow Patch 1: the reverse order leaves a window in which a monitor
whose clock was never anchored reports spurious violations.  Patch 3
adds KUnit coverage for both paths.

Patch 4 is the framework fix tlob depends on.  tlob arms its hrtimer from
sched_switch and destroys storage from the uprobe and exit paths, so a
reader that resolved the storage before hash_del_rcu() can re-arm past
the teardown's cancel and leave the timer queued on a pool slot that is
freed a grace period later:
  CPU2 (sched event for T)            CPU1 (echo -:NAME >> monitor)
  ha_setup_invariants() reads OPEN
  hrtimer_start(): waits on           xchg(&rec->state, TLOB_RETIRED)
  hrtimer_cpu_base->lock              cancel (takes the same lock first)
                                      hash_del_rcu(), call_rcu()
  enqueues the hrtimer
                                      GP ends, slot returned to the pool

Both sides contend for the same hrtimer_cpu_base->lock, so a normal lock
handoff is enough.  The state recheck in ha_setup_invariants() narrows
the window but cannot close it: the reader resolved the storage before
the unlink, so the first point past it is the RCU callback.  Patch 5 adds
KUnit coverage for the new free hook.

Series structure
================

Fixes to the framework, independent of tlob:

Patch 1: rv: Force environment reset action on HA monitor start
  Cherry-picked from Gabriele's patch.  Instead of starting
  with the ENV_INVALID_VALUE sentinel and waiting for the first explicit
  reset, ha_monitor_reset_all_stored() now calls the monitor-supplied
  ha_reset_env(), which sets a fresh clock anchor for ns-granularity
  clocks.  Monitors with no stored variable define HA_NO_RESET, emitted
  automatically by rvgen.  This removes the invalid-env special case
  from constraint checks and lets tlob re-anchor its clock on restart.
  Carried unchanged from Gabriele's reply to v6; it is not upstream yet.
  If it lands through his tree first, it will be dropped from this
  series.

Patch 2: rv: Fix ha_monitor timer and invariant underflow
  Clamp the HA timer remainder to 0 (fire immediately) when the budget
  already elapsed, and rewrite the invariant check so neither side can
  underflow.  Without it a late state transition arms the timer far into
  the future and the monitor stops enforcing the budget.

Patch 3: rv: Add a KUnit test for the HA timer underflow
  Covers both paths: the invariant holds with the uptime below the
  budget, and a timer armed after the budget elapsed expires immediately.

Patch 4: rv: Disarm HA timers on the storage free path
  Add da_monitor_free_hook(), called right before a per-object storage
  is freed, and have HA cancel its timer there; kfree_rcu() becomes
  call_rcu() so the RCU free path has a callback to hook.

Patch 5: rv: Add a KUnit test for the storage free path
  KUnit coverage for the Patch 4 free hook.

New infrastructure:

Patch 6: rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable
  #ifndef guards for da_monitor_reset_hook and EVENT_NONE_LBL.

Patch 7: rv: Introduce DA_MON_ALLOCATION_STRATEGY
  Compile-time allocation strategy selector for per-object DA storage:
  DA_ALLOC_AUTO (default kmalloc), DA_ALLOC_POOL (pre-allocated mempool),
  DA_ALLOC_MANUAL (caller-managed).  DA_MON_POOL_SIZE alone selects pool
  mode.

Patch 8: rv: Add per-object teardown hook da_extra_cleanup
  Split out of the tlob patch per Gabriele's request, so the framework
  change can be reviewed on its own.

Patch 9: rv: Add generic uprobe infrastructure for RV monitors
  Thin wrapper over uprobe_consumer for path resolution, registration,
  and safe synchronous teardown.  struct rv_uprobe embeds
  uprobe_consumer directly and holds an inode reference for the probed
  binary for its lifetime.  Also introduces config RV_UPROBE and the
  Makefile rule to keep git bisect cleanly passing across the series.

tlob:

Patch 10: rv: Add tlob model DOT file
  Graphviz DOT specification of the tlob hybrid automaton.

Patch 11: rv: Add tlob hybrid automaton monitor
  Main tlob implementation.

Patch 12: rv: Add KUnit tests for the tlob monitor
  Parser tests reached through a const rv_tlob_kunit_ops struct of
  function pointers, covering valid lines, malformed inputs, and
  out-of-range thresholds.

Patch 13: selftests/verification: Add tlob selftests
  Eleven ftrace-style selftests covering binding, violation, multi-binding,
  multi-task sharing of one binding, overlap registration rejection,
  unbind/disable on live tasks, start/stop restart, and the per-state
  breakdown derived from event_tlob.

Patch 14: selftests/ftrace: Walk up parent dirs to find test.d/functions
  The ftracetest walk-up change needed to run the verification tests
  from the verification subdirectory.

Changes since v6

================

The series is now grouped: the framework fixes come first, then the new
infrastructure, then tlob.

New in this revision:
  - Patches 1 and 2: the HA environment lifecycle and timer fixes, in
    that order (see above).  Patch 3 adds KUnit coverage for both.

  - Patch 4 (was 1): the HA timer teardown fix, found while auditing
    tlob's own teardown.  The gap is in the framework; a monitor cannot
    close it.  Patch 5 adds KUnit coverage, and Patch 8 splits the
    per-object teardown hook out of the tlob patch.

  - Dropped detail_env_tlob and the per-state accounting.  Which phase
    consumed the budget is now derived from event_tlob in user space;
    monitor_tlob.rst documents the recipe.  This removes the per-record
    lock and the accumulator from the hot path, and tlob no longer
    deviates from "run a model and spot violations" (Gabriele, v2).

  - The model gives stopped a self-loop for every scheduler event, and the
    sched handlers call da_handle_event() unconditionally.  The
    source-side gate that skipped events while stopped is gone; the logic
    lives in the model (Gabriele, v6).

  - tlob_sym.c (a 235-line ELF parser) is replaced by a sym_offset()
    shell helper built on readelf (Gabriele, v3).

  - tlob_preempt_work, identical to tlob_busy_work, is dropped and the
    busy-wait body is shared (Gabriele, v3).

  - The KUnit and DOT commit messages were rewritten as requested
    (Gabriele, v6).

  - Dropped three patches that no longer stand on their own: "rv: Fix
    ha_invariant_passed_ns silent bypass of invariant check" (Patch 1
    makes it unnecessary), "rv: Check the decoded fields in the tlob
    parser tests" and "selftests/verification: Fix up the tlob selftests"
    (folded into the patches they fix, so each patch is complete on its
    own).

Also folded into the patches they belong to, from earlier review rounds:
the DA_MON_POOL_SIZE dual-macro simplification and DA_ALLOC_MANUAL
(Gabriele), the invariant-check rewrite (sashiko-bot), and the tlob
teardown/error-path fixes: security_locked_down(LOCKDOWN_TRACEFS) in
tlob_monitor_open(), the single tlob_record_state word with
TLOB_RETIRED as the only licence to free, tlob_add_uprobe()'s rollback
running the full nosync/quiesce/sync/reap sequence, and
tlob_remove_all_uprobes() holding tlob_uprobe_mutex across the sync and
the reap.

Testing
=======
Environment: x86_64, CONFIG_PREEMPT_RT=y, kernel built with virtme-ng
(vng --build), selftests built on the host and run inside the VM via
`ftracetest --rv ../verification` (the verificationtest-ktap entry
point, with logs redirected to /tmp):
- KUnit: rv_mon suite (CONFIG_RV_MONITORS_KUNIT_TEST), rv_test_tlob case
  covering the uprobe-line parser
- 11 ftrace-style selftests: uprobe_bind, uprobe_violation,
  uprobe_no_event, uprobe_multi, uprobe_multi_instance,
  uprobe_detail_{running,sleeping,waiting}, uprobe_restart,
  uprobe_overlap_register, uprobe_restart_violation
Both suites pass.  The pre-existing rv_* tests in the same directory
and the rest of the rv_mon KUnit suite (nomiss, sco, sssw, sts, opid,
pagefault, sleep) keep passing as well.

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 and EVENT_NONE_LBL overridable
  rv: Introduce DA_MON_ALLOCATION_STRATEGY
  rv: Add per-object teardown hook da_extra_cleanup
  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       |  154 +++
 include/rv/da_monitor.h                       |  154 ++-
 include/rv/ha_monitor.h                       |   61 +-
 include/rv/rv_uprobe.h                        |   35 +
 kernel/trace/rv/Kconfig                       |    5 +
 kernel/trace/rv/Makefile                      |    2 +
 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          | 1049 +++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h          |  142 +++
 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_monitors_test.c            |  162 +++
 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_breakdown.tc           |  136 +++
 .../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_no_event.tc            |   17 +
 .../test.d/tlob/uprobe_overlap_register.tc    |   66 ++
 .../test.d/tlob/uprobe_restart.tc             |   66 ++
 .../test.d/tlob/uprobe_restart_violation.tc   |   48 +
 .../test.d/tlob/uprobe_unbind_live.tc         |   65 +
 .../test.d/tlob/uprobe_violation.tc           |   53 +
 .../selftests/verification/tlob_target.c      |  127 ++
 tools/verification/models/tlob.dot            |   27 +
 tools/verification/rvgen/rvgen/dot2c.py       |    2 +
 38 files changed, 2930 insertions(+), 20 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_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_breakdown.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_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_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_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] 17+ messages in thread

* [PATCH v7 01/14] rv: Force environment reset action on HA monitor start
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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	[flat|nested] 17+ messages in thread

* [PATCH v7 02/14] rv: Fix ha_monitor timer and invariant underflow
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
  2026-10-11 19:39 ` [PATCH v7 01/14] rv: Force environment reset action on HA monitor start wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

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.

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 replace the invariant check as store + expire_ns >= time_ns so
neither side can underflow.

Fixes: f5587d1b6ec9 ("rv: Add Hybrid Automata monitor type")
Fixes: ab2900ae252b ("rv: Simplify hybrid automata monitors's clock variables")
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 include/rv/ha_monitor.h | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 93ac99ff7f1d..499ce60779f9 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -351,7 +351,7 @@ 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;
+	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 +410,11 @@ 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);
 }
 
+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 +444,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 +501,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 +510,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	[flat|nested] 17+ messages in thread

* [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
  2026-10-11 19:39 ` [PATCH v7 01/14] rv: Force environment reset action on HA monitor start wen.yang
  2026-10-11 19:39 ` [PATCH v7 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-12  7:47   ` kernel test robot
  2026-10-12  9:27   ` kernel test robot
  2026-10-11 19:39 ` [PATCH v7 04/14] rv: Disarm HA timers on the storage free path wen.yang
                   ` (10 subsequent siblings)
  13 siblings, 2 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

Add rvtest, a minimal per-object HA monitor, and a test for 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.

Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 kernel/trace/rv/rv_monitors_test.c | 122 +++++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)

diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index 3ad11195e664..889a209f6435 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -13,6 +13,87 @@
 #include <linux/rv.h>
 #include "rv.h"
 
+#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;
+
+/* The framework calls these for every monitor; this one reports nothing. */
+#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 },
+		{ INVALID_STATE },
+	},
+	.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);
+}
+
+/* The tests arm the timer themselves. */
+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 = RV_MON_OPS_INIT();
+
 /*
  * An easy way to pass the context is to use kunit_get_current_test()->priv,
  * but this doesn't always work (e.g. a reactor running from another context
@@ -146,6 +227,46 @@ static void rv_test_dummy(struct kunit *test)
 	active_ctx = NULL;
 }
 
+/*
+ * 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;
+
+	prepare_test(test, &rvtest_mon);
+	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.
+	 */
+	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);
+}
+
 #include "monitors/sco/sco_kunit.c"
 #include "monitors/sssw/sssw_kunit.c"
 #include "monitors/sts/sts_kunit.c"
@@ -163,6 +284,7 @@ static struct kunit_case rv_mon_test_cases[] = {
 	KUNIT_CASE(rv_test_nomiss),
 	KUNIT_CASE(rv_test_pagefault),
 	KUNIT_CASE(rv_test_sleep),
+	KUNIT_CASE(rv_test_rvtest_underflow),
 	{}
 };
 
-- 
2.25.1


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

* [PATCH v7 04/14] rv: Disarm HA timers on the storage free path
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (2 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 05/14] rv: Add a KUnit test for " wen.yang
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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 with ha_cancel_timer_sync().  Replace kfree_rcu() with
call_rcu() so there is a callback to hook.

A storage whose monitor never started has a zeroed timer, and
hrtimer_cancel() dereferences timer->base.  nomiss pre-creates one such
storage per possible CPU.  Skip them with the new ha_timer_ready().

Fixes: 4a24127bd6cb ("rv: Add support for per-object monitors in DA/HA")
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 include/rv/da_monitor.h | 23 ++++++++++++++++++++++-
 include/rv/ha_monitor.h | 26 ++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index e3cf85c9ce55..2cd260208bc1 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,7 @@ 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);
+	call_rcu(&mon_storage->rcu, da_kfree_cb);
 }
 
 static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -607,6 +627,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 499ce60779f9..99b60d0d26b6 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -29,6 +29,8 @@ 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 bool ha_timer_ready(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 +40,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
 /*
@@ -467,8 +470,15 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
 {
 	return timer_delete(&ha_mon->timer);
 }
+/* timer_setup() sets it; the cancel paths here never clear it. */
+static inline bool ha_timer_ready(struct ha_monitor *ha_mon)
+{
+	return READ_ONCE(ha_mon->timer.function);
+}
 static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon)
 {
+	if (!ha_timer_ready(ha_mon))
+		return;
 	timer_delete_sync(&ha_mon->timer);
 }
 #elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
@@ -524,8 +534,18 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
 {
 	return hrtimer_try_to_cancel(&ha_mon->hrtimer) == 1;
 }
+/*
+ * hrtimer_cancel() reads timer->base before anything else; a zeroed timer
+ * has it NULL and would fault.
+ */
+static inline bool ha_timer_ready(struct ha_monitor *ha_mon)
+{
+	return READ_ONCE(ha_mon->hrtimer.base);
+}
 static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon)
 {
+	if (!ha_timer_ready(ha_mon))
+		return;
 	hrtimer_cancel(&ha_mon->hrtimer);
 }
 #else /* HA_TIMER_NONE */
@@ -538,9 +558,15 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
 {
 	return false;
 }
+static inline bool ha_timer_ready(struct ha_monitor *ha_mon) { return false; }
 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)
+{
+	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	[flat|nested] 17+ messages in thread

* [PATCH v7 05/14] rv: Add a KUnit test for the storage free path
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (3 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 04/14] rv: Disarm HA timers on the storage free path wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 06/14] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable wen.yang
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

The test resolves a storage, desstroys it with da_destroy_storage(), and
then arms the HA timer through the stale pointer. The free hook must
disarm that timer too, so it never fires and no reaction is reported.

Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 kernel/trace/rv/rv_monitors_test.c | 38 ++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index 889a209f6435..b30e49419e34 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -267,6 +267,43 @@ static void rv_test_rvtest_underflow(struct kunit *test)
 	ha_cancel_timer_sync(ha_mon);
 }
 
+/*
+ * 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.
+ */
+static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
+{
+	struct rv_kunit_ctx *ctx = test->priv;
+	const int id = 4242;
+	struct da_monitor *stale;
+
+	prepare_test(test, &rvtest_mon);
+
+	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();
+
+	msleep(600);
+	RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+}
+
 #include "monitors/sco/sco_kunit.c"
 #include "monitors/sssw/sssw_kunit.c"
 #include "monitors/sts/sts_kunit.c"
@@ -285,6 +322,7 @@ static struct kunit_case rv_mon_test_cases[] = {
 	KUNIT_CASE(rv_test_pagefault),
 	KUNIT_CASE(rv_test_sleep),
 	KUNIT_CASE(rv_test_rvtest_underflow),
+	KUNIT_CASE(rv_test_rvtest_arm_after_destroy),
 	{}
 };
 
-- 
2.25.1


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

* [PATCH v7 06/14] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (4 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 05/14] rv: Add a KUnit test for " wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

Wrap both definitions with #ifndef guards so HA-based monitors can
substitute their own implementations before including this header.
tlob uses this to define a reset hook that cancels per-task hrtimers
on monitor teardown.

Overrides must still call ha_monitor_reset_env() or cancel outstanding
timers to avoid timer UAF.

No behaviour change for monitors that do not override either macro.

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 99b60d0d26b6..b8c74d4d4064 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -38,8 +38,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
@@ -78,7 +84,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	[flat|nested] 17+ messages in thread

* [PATCH v7 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (5 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 06/14] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 08/14] rv: Add per-object teardown hook da_extra_cleanup wen.yang
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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 2cd260208bc1..f9af56304c9f 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
  *
@@ -584,7 +662,10 @@ 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);
-	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 *))
@@ -610,6 +691,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;
 }
 
@@ -629,20 +717,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	[flat|nested] 17+ messages in thread

* [PATCH v7 08/14] rv: Add per-object teardown hook da_extra_cleanup
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (6 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

da_monitor_destroy() frees each per-object storage, but a monitor
whose target is allocated separately has no point to free it: the
target is still reachable from the storage until hash_del_rcu().

Add an optional da_extra_cleanup() hook, called for each storage after
da_monitor_free_hook() has disarmed its timer and before the storage is
unlinked.  It defaults to a no-op.  tlob, added later in this series,
uses it to retire and free its per-window record.

Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 include/rv/da_monitor.h | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index f9af56304c9f..97d9e876c891 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -116,6 +116,16 @@ static struct rv_monitor rv_this;
 #define da_monitor_free_hook(da_mon)
 #endif
 
+/*
+ * Per-object teardown hook, called for each entry after
+ * da_monitor_reset_all() + da_monitor_sync_hook() and before hash_del_rcu().
+ * Runs after da_monitor_free_hook(), so it may free the storage's target.
+ * Default: no-op.
+ */
+#ifndef da_extra_cleanup
+#define da_extra_cleanup(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
@@ -715,7 +725,9 @@ 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) {
+		/* Before da_extra_cleanup(): it frees what the timer reads. */
 		da_monitor_free_hook(&mon_storage->rv.da_mon);
+		da_extra_cleanup(&mon_storage->rv.da_mon);
 		hash_del_rcu(&mon_storage->node);
 		if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
 			mempool_free(mon_storage, &da_monitor_pool);
-- 
2.25.1


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

* [PATCH v7 09/14] rv: Add generic uprobe infrastructure for RV monitors
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (7 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 08/14] rv: Add per-object teardown hook da_extra_cleanup wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 10/14] rv: Add tlob model DOT file wen.yang
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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 cdbf68c84f5a..e295c9eb8401 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	[flat|nested] 17+ messages in thread

* [PATCH v7 10/14] rv: Add tlob model DOT file
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (8 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 11/14] rv: Add tlob hybrid automaton monitor wen.yang
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 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	[flat|nested] 17+ messages in thread

* [PATCH v7 11/14] rv: Add tlob hybrid automaton monitor
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (9 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 10/14] rv: Add tlob model DOT file wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 12/14] rv: Add KUnit tests for the tlob monitor wen.yang
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

tlob (task latency over budget) is a per-task hybrid automaton RV
monitor that tracks wall-clock time across a user-delimited code
section and emits an error when the elapsed time exceeds a configurable
budget.

The automaton has four states (running, waiting, sleeping, stopped),
driven by the sched_switch and sched_wakeup tracepoints plus a
user-visible start/stop pair.  stop only fires from running and parks
the window in stopped, where a later start restarts it in place.  A
wakeup is a self-loop on running and waiting, since an already queued
task can still be woken without leaving its budget window; the elapsed
time is always accounted to the state the monitor is leaving.  The
clk_elapsed < BUDGET_NS() invariant is enforced by a per-task
HRTIMER_MODE_REL_HARD timer; on expiry the monitor records a per-state
breakdown (running_ns, waiting_ns, sleeping_ns) before emitting
error_env_tlob.

Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 Documentation/trace/rv/index.rst           |    1 +
 Documentation/trace/rv/monitor_tlob.rst    |  154 +++
 kernel/trace/rv/Kconfig                    |    1 +
 kernel/trace/rv/Makefile                   |    1 +
 kernel/trace/rv/monitors/tlob/Kconfig      |   12 +
 kernel/trace/rv/monitors/tlob/tlob.c       | 1038 ++++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h       |  142 +++
 kernel/trace/rv/monitors/tlob/tlob_trace.h |   20 +
 kernel/trace/rv/rv_trace.h                 |    1 +
 9 files changed, 1370 insertions(+)
 create mode 100644 Documentation/trace/rv/monitor_tlob.rst
 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_trace.h

diff --git a/Documentation/trace/rv/index.rst b/Documentation/trace/rv/index.rst
index 29769f06bb0f..1501545b5f08 100644
--- a/Documentation/trace/rv/index.rst
+++ b/Documentation/trace/rv/index.rst
@@ -16,5 +16,6 @@ Runtime Verification
    monitor_wwnr.rst
    monitor_sched.rst
    monitor_rtapp.rst
+   monitor_tlob.rst
    monitor_stall.rst
    monitor_deadline.rst
diff --git a/Documentation/trace/rv/monitor_tlob.rst b/Documentation/trace/rv/monitor_tlob.rst
new file mode 100644
index 000000000000..e3171d0e4441
--- /dev/null
+++ b/Documentation/trace/rv/monitor_tlob.rst
@@ -0,0 +1,154 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Monitor tlob
+============
+
+- Name: tlob - task latency over budget
+- Type: per-object hybrid automaton (RV_MON_PER_OBJ)
+- Author: Wen Yang <wen.yang@linux.dev>
+
+Description
+-----------
+
+The tlob (task latency over budget) monitor is a per-object hybrid
+automaton that checks a task's elapsed time (CLOCK_MONOTONIC) against a
+budget: from an instrumented entry point to its matching return, the
+task must not spend more than the budget in any combination of running,
+waiting and sleeping.  The clock ``clk_elapsed`` must satisfy
+``clk_elapsed <= BUDGET_NS()`` in the ``running``, ``waiting`` and
+``sleeping`` states; when it does not, the HA timer fires, the framework
+emits ``error_env_tlob`` and resets the monitor::
+
+                                           | (initial)
+                                           v
+        +--------------+   stop    +----------+
+     +->|   running    | --------> |  stopped |
+     |  +--------------+ <-start-- +----------+
+   switch_in  preempt  sleep
+     |        |       |
+     |        |       |
+     |        v       v
+     +---------+  +---------+
+     | waiting |  | sleeping|
+     +---------+  +---------+
+          ^            v
+          |   wakeup   |
+          |            |
+          +------------+
+
+Not drawn above, ``running`` and ``waiting`` each take ``wakeup`` as a
+self-loop: a task woken between ``set_current_state()`` and ``schedule()``
+is still on CPU, and a wakeup never ends the budget window.  ``stopped``
+takes every scheduler event (``preempt``, ``sleep``, ``switch_in``,
+``wakeup``) as a self-loop, so a task keeps going through the scheduler
+between windows without disturbing the model.  See
+``tools/verification/models/tlob.dot`` for the full model.
+
+``stopped`` has no invariant and hence no timer.  A ``stop``
+(``tlob_stop_task()``, window ended) does not reset the monitor: a later
+``start`` (``tlob_start_task()``) begins a new window from ``stopped``,
+while task exit, uprobe removal and monitor disable are the paths that
+reset and free the state.  A START while still in ``running`` (a nested
+window) is rejected with ``-EALREADY``.
+
+The non-running condition (monitor not yet started, or reset after a
+violation or monitor disable) is handled implicitly by the RV framework;
+it is not an explicit DA state.
+
+Usage
+-----
+
+tracefs interface (uprobe-based external monitoring)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``monitor`` tracefs file instruments an unmodified binary via
+uprobes, following the ftrace ``uprobe_events`` convention.  Each
+binding has a NAME::
+
+  p:NAME PATH:OFFSET_START OFFSET_STOP threshold=NS
+
+The probes at ``OFFSET_START`` and ``OFFSET_STOP`` (ELF file offsets of
+entry points in ``PATH``) call ``tlob_start_task()`` and
+``tlob_stop_task()``.  ``PATH`` may contain ``:``; the last ``:`` in
+``PATH:OFFSET_START`` is the separator.  ``threshold`` is in
+nanoseconds and must be between 1 us and 1 hour.  ``NAME`` follows the
+ftrace event-name rule (``[A-Za-z_][A-Za-z0-9_]*``, 31 characters at
+most) and must be unique among the registered bindings
+(``-EEXIST``); overlapping ranges on the same binary are rejected
+too, since a task runs one window at a time and a second range would
+shadow the first::
+
+  echo 1 > /sys/kernel/tracing/rv/monitors/tlob/enable
+
+  echo "p:myregion /usr/bin/myapp:0x12a0 0x12f0 threshold=5000000" \
+      >> /sys/kernel/tracing/rv/monitors/tlob/monitor
+
+  # List registered bindings
+  cat /sys/kernel/tracing/rv/monitors/tlob/monitor
+
+  # Remove the binding NAME
+  echo "-:myregion" >> /sys/kernel/tracing/rv/monitors/tlob/monitor
+
+As with the ftrace dynamic-event files, opening ``monitor`` for write
+with ``>`` truncates: ``echo > monitor`` removes all bindings.  Use
+``>>`` for every individual command, removals included.
+
+A task may hit the START/STOP pair any number of times: each pair of
+hits is one window, and the per-task record is kept between windows.  A
+window ends at whichever comes first, the STOP probe or the timer; if
+the timer won, the STOP is a no-op and the next START opens the
+following window.  Removing a binding while a task is between windows
+frees that task's record; a task still inside a window keeps running and
+is cleaned up on exit.  A task reaching a range bound to a different
+binding while its record is between windows is reported through
+``error_tlob``; while a window is open, any START is refused with
+``-EALREADY`` and not reported.
+
+Violation tracepoints
+~~~~~~~~~~~~~~~~~~~~~
+
+A budget violation emits the framework tracepoint ``error_env_tlob``:
+``id`` (task pid), ``state``, ``event`` (``"budget_exceeded"``) and
+``env`` (``clk_elapsed``).
+
+Deriving the per-state breakdown
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Which phase consumed the budget (``running``: compute, ``waiting``:
+scheduler pressure, ``sleeping``: I/O) is recovered from ``event_tlob``,
+the standard state-transition tracepoint.  Each window is a run of
+``event_tlob`` lines for one pid: ``start`` opens it, ``stop`` closes it,
+and a violation closes it with ``error_env_tlob``.  Attribute the time
+between consecutive lines to the ``next_state`` of the earlier one::
+
+  echo mono > /sys/kernel/tracing/trace_clock
+  echo 1 > /sys/kernel/tracing/events/rv/event_tlob/enable
+  echo 1 > /sys/kernel/tracing/events/rv/error_env_tlob/enable
+  trace-cmd record -e rv:event_tlob -e rv:error_env_tlob
+  # ... run workload ...
+  trace-cmd report
+
+The ``mono`` clock is required: ``event_tlob`` timestamps are compared
+across CPUs because a task migrates during a window.  The three buckets
+sum to ``clk_elapsed`` up to the tracepoint overhead.
+
+Design notes
+------------
+
+- A fresh window always starts in ``running``: monitoring a task that
+  is already waiting or sleeping at START misattributes the first
+  interval to ``running``.
+- ``TASK_STOPPED`` and ``TASK_TRACED`` carry ``prev_state != 0`` and
+  count as ``sleeping``, indistinguishable from I/O-blocked time.
+- A task preempted between ``set_current_state()`` and ``schedule()``
+  carries ``prev_state != 0`` and counts as ``sleeping`` although it
+  stays runnable.
+- ``sched_wakeup_new`` is not hooked: a task is attached by a START
+  probe in its own context, after the initial wakeup.
+
+Specification
+-------------
+
+- Graphviz Dot file: tools/verification/models/tlob.dot
+- KUnit tests: kernel/trace/rv/monitors/tlob/tlob_kunit.c
+- User-space tests: tools/testing/selftests/verification/
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 2bc412304293..222b3bea8079 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -84,6 +84,7 @@ source "kernel/trace/rv/monitors/deadline/Kconfig"
 source "kernel/trace/rv/monitors/nomiss/Kconfig"
 # Add new deadline monitors here
 
+source "kernel/trace/rv/monitors/tlob/Kconfig"
 # Add new monitors here
 
 config RV_UPROBE
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index e295c9eb8401..cd0ec11f0e05 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
 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
+obj-$(CONFIG_RV_MON_TLOB) += monitors/tlob/tlob.o
 # Add new monitors here
 obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
diff --git a/kernel/trace/rv/monitors/tlob/Kconfig b/kernel/trace/rv/monitors/tlob/Kconfig
new file mode 100644
index 000000000000..4f072a9a4da2
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TLOB
+	bool "tlob monitor"
+	depends on RV && UPROBES && HIGH_RES_TIMERS
+	select HA_MON_EVENTS_ID
+	select RV_UPROBE
+	help
+	  Enable the tlob (task latency over budget) hybrid-automaton RV
+	  monitor.  tlob tracks per-task elapsed time across a
+	  user-delimited code section and emits a violation when the
+	  elapsed time exceeds a configurable per-invocation budget.
diff --git a/kernel/trace/rv/monitors/tlob/tlob.c b/kernel/trace/rv/monitors/tlob/tlob.c
new file mode 100644
index 000000000000..066a2f0663d5
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob.c
@@ -0,0 +1,1038 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tlob: task latency over budget monitor
+ *
+ * Flags budget overruns of a marked user-space code path: the hrtimer
+ * callback emits error_env_tlob on violation.  The per-state
+ * (running/waiting/sleeping) breakdown is derived from event_tlob in user
+ * space (see Documentation/trace/rv/monitor_tlob.rst).
+ *
+ * RV_MON_PER_OBJ: the per-task record lives as monitor_target in the
+ * framework's hash table.  One HA clock invariant: clk_elapsed <
+ * BUDGET_NS() in running/waiting/sleeping (stopped exempt).
+ *
+ * Copyright (C) 2026 Wen Yang <wen.yang@linux.dev>
+ */
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/mempool.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/namei.h>
+#include <linux/security.h>
+#include <linux/rv.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <rv/instrumentation.h>
+#include <rv/rv_uprobe.h>
+#include <rv.h>
+
+#define MODULE_NAME "tlob"
+
+#include <trace/events/sched.h>
+#include <rv_trace.h>
+
+struct tlob_uprobe_binding;
+
+/*
+ * The record's lifecycle -- not the task's scheduler state and not the
+ * automaton's curr_state:
+ *   TLOB_OPEN    - owns the live window
+ *   TLOB_CLOSED  - the window ended, the record waits for a restart
+ *   TLOB_RETIRED - a teardown path claimed the record; it is never reused
+ */
+enum tlob_record_state {
+	TLOB_OPEN,
+	TLOB_CLOSED,
+	TLOB_RETIRED,
+};
+
+/*
+ * One per-task monitoring record, reused across that task's windows: from
+ * tlob_rec_pool, hashed as monitor_target, freed via call_rcu.
+ */
+struct tlob_record {
+	atomic_t		state;
+	u64			threshold_ns;
+
+	pid_t			id;
+	/*
+	 * Owner binding, NULL once claimed for teardown.  started_node is then
+	 * owned by the claimer's list instead.  Both under tlob_binding_lock.
+	 */
+	struct tlob_uprobe_binding	*binding;
+	struct list_head	started_node;
+
+	struct rcu_head		rcu;
+};
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+
+typedef struct tlob_record *monitor_target;
+
+static inline void tlob_reset_notify(struct da_monitor *da_mon);
+#define da_monitor_reset_hook tlob_reset_notify
+
+static inline void tlob_extra_cleanup(struct da_monitor *da_mon);
+#define da_extra_cleanup tlob_extra_cleanup
+
+#define EVENT_NONE_LBL "budget_exceeded"
+
+#include "tlob.h"
+
+#define DA_MON_POOL_SIZE TLOB_MAX_MONITORED
+
+#include <rv/ha_monitor.h>
+
+/*
+ * Reset hook: claim the window end so the record can be restarted.  Runs on
+ * timer expiry (violation), teardown and disable; stop uses "stop".
+ */
+static inline void tlob_reset_notify(struct da_monitor *da_mon)
+{
+	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+	struct tlob_record *rec;
+
+	/* Teardown disarms via da_monitor_free_hook() and frees rec. */
+	if (unlikely(READ_ONCE(ha_mon_destroying)))
+		return;
+
+	ha_monitor_reset_env(da_mon);
+
+	rec = ha_get_target(ha_mon);
+	if (!rec)
+		return;
+
+	/* A stop or teardown may have taken the window first. */
+	atomic_cmpxchg(&rec->state, TLOB_OPEN, TLOB_CLOSED);
+}
+
+#define BUDGET_NS(ha_mon) (ha_get_target(ha_mon)->threshold_ns)
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_tlob env,
+		      u64 time_ns)
+{
+	if (env == clk_elapsed_tlob)
+		return ha_get_clk_ns(ha_mon, env, time_ns);
+	return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_tlob env,
+			 u64 time_ns)
+{
+	if (env == clk_elapsed_tlob)
+		ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * clk_elapsed <= BUDGET_NS in running/waiting/sleeping; stopped exempt.
+ *
+ * tlob needs no guards: env_store stays in guard (anchor) form for the whole
+ * window, so ha_inv_to_guard() must never be applied to it; that would
+ * subtract BUDGET_NS and skew every check by one budget.
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+					enum states curr_state,
+					enum events event,
+					enum states next_state,
+					u64 time_ns)
+{
+	if (curr_state == stopped_tlob)
+		return true;
+	return ha_check_invariant_ns(ha_mon, clk_elapsed_tlob, time_ns,
+				     BUDGET_NS(ha_mon));
+}
+
+/*
+ * Only start restarts a closed window; a sched event racing the window
+ * end must not re-arm the timer.  Restart re-anchors the clock to the
+ * event time; a stale anchor would underflow the timer delay.
+ */
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+				       enum states curr_state,
+				       enum events event,
+				       enum states next_state,
+				       u64 time_ns)
+{
+	struct tlob_record *rec = ha_get_target(ha_mon);
+
+	if (next_state == stopped_tlob)
+		return;
+
+	if (next_state >= state_max_tlob) {
+		ha_cancel_timer(ha_mon);
+		return;
+	}
+
+	if (event != start_tlob) {
+		/* A sched event racing the window end must not re-arm. */
+		if (atomic_read_acquire(&rec->state) != TLOB_OPEN)
+			return;
+		ha_start_timer_ns(ha_mon, clk_elapsed_tlob,
+				  BUDGET_NS(ha_mon), time_ns);
+		return;
+	}
+
+	ha_reset_clk_ns(ha_mon, clk_elapsed_tlob, time_ns);
+	ha_start_timer_ns(ha_mon, clk_elapsed_tlob, BUDGET_NS(ha_mon),
+			  time_ns);
+	/* Reopen only a closed record; a teardown claim keeps it closed. */
+	atomic_cmpxchg_release(&rec->state, TLOB_CLOSED, TLOB_OPEN);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+				 enum states curr_state, enum events event,
+				 enum states next_state, u64 time_ns)
+{
+	if (!ha_verify_invariants(ha_mon, curr_state, event,
+				  next_state, time_ns))
+		return false;
+
+	ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+	return true;
+}
+
+/*
+ * TLOB_MAX_MONITORED pre-allocated slots: bounded start latency,
+ * -ENOSPC past the cap; mempool_free() is RCU-callback safe.
+ *
+ * A published slot owes readers a grace period, so every return goes through
+ * the RCU callback.
+ */
+static mempool_t tlob_rec_pool;
+
+static void tlob_rec_return_cb(struct rcu_head *head)
+{
+	struct tlob_record *rec =
+		container_of(head, struct tlob_record, rcu);
+
+	mempool_free(rec, &tlob_rec_pool);
+}
+
+static struct tlob_record *tlob_rec_alloc(void)
+{
+	struct tlob_record *rec =
+		mempool_alloc_preallocated(&tlob_rec_pool);
+
+	if (!rec)
+		return NULL;
+
+	memset(rec, 0, sizeof(*rec));
+	INIT_LIST_HEAD(&rec->started_node);
+	return rec;
+}
+
+/* Protected by tlob_uprobe_mutex. */
+static LIST_HEAD(tlob_uprobe_list);
+static DEFINE_MUTEX(tlob_uprobe_mutex);
+
+/*
+ * Serialises tlob_record ownership: restart, detach, unbind reap.  Lock order:
+ * tlob_uprobe_mutex first.  Critical sections touch only lists and atomics, so
+ * a PREEMPT_RT promotion is safe.
+ */
+static DEFINE_SPINLOCK(tlob_binding_lock);
+
+/* Per-binding state: a start + stop probe pair for one binary region. */
+struct tlob_uprobe_binding {
+	struct list_head	list;
+	char			name[TLOB_MAX_NAME];
+	u64			threshold_ns;
+	char			binpath[TLOB_MAX_PATH];
+	loff_t			offset_start;
+	loff_t			offset_stop;
+	/*
+	 * Keeps the dentry hashed: later lookups of binpath (exec, re-add)
+	 * must hit the inode the probes are keyed on.
+	 */
+	struct path		path;
+	/* Records started here.  tlob_binding_lock. */
+	struct list_head	started_list;
+	/* Claimed by quiesce, destroyed by reap.  tlob_binding_lock. */
+	struct list_head	quiesced_list;
+	struct rv_uprobe		start_probe;
+	struct rv_uprobe		stop_probe;
+};
+
+static inline void tlob_detach_from_binding(struct tlob_record *rec)
+{
+	guard(spinlock)(&tlob_binding_lock);
+
+	if (!rec->binding)
+		return;
+	rec->binding = NULL;
+	list_del_init(&rec->started_node);
+}
+
+/*
+ * Per-record teardown during monitor disable.  TLOB_RETIRED claims it, so an
+ * exit or unbind racing disable frees the record once.
+ */
+static inline void tlob_extra_cleanup(struct da_monitor *da_mon)
+{
+	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+	struct tlob_record *rec = ha_get_target(ha_mon);
+
+	if (!rec)
+		return;
+
+	if (atomic_xchg(&rec->state, TLOB_RETIRED) == TLOB_RETIRED)
+		return;
+
+	tlob_detach_from_binding(rec);
+	call_rcu(&rec->rcu, tlob_rec_return_cb);
+}
+
+static void handle_sched_switch(void *data, bool preempt,
+				struct task_struct *prev,
+				struct task_struct *next,
+				unsigned int prev_state)
+{
+	/* Preemption leaves the task on the runqueue whatever __state says. */
+	bool prev_preempted = preempt || !prev_state;
+
+	da_handle_event(prev->pid, NULL,
+			prev_preempted ? preempt_tlob : sleep_tlob);
+	da_handle_event(next->pid, NULL, switch_in_tlob);
+}
+
+/*
+ * handle_sched_wakeup - sleeping -> waiting.  A task woken before it reached
+ * schedule() is still on CPU, hence the running self-loop.
+ */
+static void handle_sched_wakeup(void *data, struct task_struct *p)
+{
+	da_handle_event(p->pid, NULL, wakeup_tlob);
+}
+
+static void tlob_stop_task(struct task_struct *task,
+			   struct tlob_uprobe_binding *binding);
+static void tlob_destroy_task(pid_t id);
+
+/*
+ * handle_sched_process_exit - clean up a task that exits without hitting
+ * its STOP uprobe.  Always running here: do_exit() runs in the task's own
+ * context; no restart can follow exit.
+ */
+static void handle_sched_process_exit(void *data, struct task_struct *p,
+				      bool group_dead)
+{
+	tlob_stop_task(p, NULL);
+	tlob_destroy_task(p->pid);
+}
+
+/*
+ * tlob_start_task - begin monitoring @task: fresh record, or restart in
+ * place when the closed record's binding matches.
+ */
+static int tlob_start_task(struct task_struct *task, u64 threshold_ns,
+			   struct tlob_uprobe_binding *binding)
+{
+	struct tlob_record *rec = NULL;
+	char *stopped_name = model_get_state_name(stopped_tlob);
+	char *start_name = model_get_event_name(start_tlob);
+	bool do_destroy = false;
+
+	if (!da_monitor_enabled())
+		return -ENODEV;
+
+	if (threshold_ns < TLOB_MIN_THRESHOLD_NS ||
+	    threshold_ns > TLOB_MAX_THRESHOLD_NS)
+		return -ERANGE;
+
+	/*
+	 * Serialise duplicate-check + pool-slot claim.  da_destroy_storage()
+	 * nests rcu_read_lock(), sleepable on PREEMPT_RT, so it is deferred
+	 * past the spinlock; TLOB_RETIRED makes rec unreachable after unlock.
+	 */
+	scoped_guard(spinlock, &tlob_binding_lock) {
+		scoped_guard(rcu) {
+			rec = da_get_target_by_id(task->pid);
+			if (rec) {
+				if (atomic_read(&rec->state) == TLOB_OPEN)
+					return -EALREADY;
+				if (atomic_read(&rec->state) == TLOB_RETIRED)
+					return -ESRCH;
+				/*
+				 * Between windows under a different binding:
+				 * not relinked; emit the shadowing.
+				 */
+				if (rec->binding != binding) {
+					trace_error_tlob(task->pid,
+							 stopped_name,
+							 start_name);
+					return -EALREADY;
+				}
+
+				/*
+				 * Restart in place.  A teardown may claim the
+				 * record after this read; the reopen below then
+				 * fails and the window stays closed.
+				 */
+				if (atomic_read(&rec->state) != TLOB_CLOSED)
+					return -ESRCH;
+				rec->threshold_ns = threshold_ns;
+				/* ha_setup_invariants() resets and reopens. */
+				if (!da_handle_start_run_event(task->pid,
+							       rec, start_tlob))
+					return -ENODEV;
+				return 0;
+			}
+		}
+
+		rec = tlob_rec_alloc();
+		if (!rec)
+			return -ENOSPC;
+
+		rec->id = task->pid;
+		rec->threshold_ns = threshold_ns;
+		rec->binding = binding;
+
+		/*
+		 * Every started_list walker holds tlob_binding_lock, so linking
+		 * after the hash publishes rec is not observable.
+		 */
+		if (da_handle_start_run_event(task->pid, rec, start_tlob)) {
+			list_add_tail(&rec->started_node,
+				      &binding->started_list);
+		} else {
+			/* Unlinked; claim it for the free. */
+			atomic_set(&rec->state, TLOB_RETIRED);
+			do_destroy = true;
+		}
+	}
+
+	if (do_destroy) {
+		/* Lock dropped; rcu_read_lock() nesting is now safe. */
+		da_destroy_storage(task->pid);
+		/*
+		 * da_prepare_storage() publishes before the enabled check that
+		 * failed above, so a reader may hold rec: defer the return.
+		 */
+		call_rcu(&rec->rcu, tlob_rec_return_cb);
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+/*
+ * tlob_stop_task - end @task's window; the record stays closed for a later
+ * restart, tlob_destroy_task() frees it.
+ */
+static void tlob_stop_task(struct task_struct *task,
+			   struct tlob_uprobe_binding *binding)
+{
+	struct ha_monitor *ha_mon;
+	struct tlob_record *rec;
+
+	scoped_guard(rcu) {
+		ha_mon = ha_get_monitor(task->pid, NULL);
+		if (!ha_mon)
+			return;
+
+		rec = ha_get_target(ha_mon);
+		if (WARN_ON_ONCE(!rec))
+			return;
+
+		/* Only the opener may end it; NULL (task exit) skips. */
+		if (binding && READ_ONCE(rec->binding) != binding)
+			return;
+
+		/* Claim the window end; the timer may have taken it first. */
+		if (atomic_cmpxchg_release(&rec->state, TLOB_OPEN,
+					   TLOB_CLOSED) != TLOB_OPEN)
+			return;
+
+		ha_cancel_timer_sync(ha_mon);
+	}
+
+	/* The DA returns to stopped; the record stays closed. */
+	da_handle_event(task->pid, NULL, stop_tlob);
+}
+
+/*
+ * tlob_destroy_claimed - teardown with TLOB_RETIRED already claimed; the claim
+ * is what makes the hash removal safe.
+ */
+static void tlob_destroy_claimed(struct tlob_record *rec)
+{
+	pid_t id = rec->id;
+
+	tlob_detach_from_binding(rec);
+
+	scoped_guard(rcu) {
+		struct ha_monitor *ha_mon = ha_get_monitor(id, NULL);
+
+		if (!WARN_ON_ONCE(!ha_mon))
+			da_monitor_reset(&ha_mon->da_mon);
+	}
+	da_destroy_storage(id);
+
+	/* Readers may still hold rec; the pool slot returns after a GP. */
+	call_rcu(&rec->rcu, tlob_rec_return_cb);
+}
+
+static void tlob_destroy_task(pid_t id)
+{
+	struct ha_monitor *ha_mon;
+	struct tlob_record *rec;
+
+	scoped_guard(rcu) {
+		ha_mon = ha_get_monitor(id, NULL);
+		if (!ha_mon)
+			return;
+		rec = ha_get_target(ha_mon);
+		if (WARN_ON_ONCE(!rec))
+			return;
+		if (atomic_xchg(&rec->state, TLOB_RETIRED) == TLOB_RETIRED)
+			return;
+	}
+
+	tlob_destroy_claimed(rec);
+}
+
+static int tlob_uprobe_entry_handler(struct uprobe_consumer *self,
+				     struct pt_regs *regs, __u64 *data)
+{
+	struct tlob_uprobe_binding *b =
+		container_of(self, struct tlob_uprobe_binding, start_probe.uc);
+
+	tlob_start_task(current, b->threshold_ns, b);
+	return 0;
+}
+
+static int tlob_uprobe_stop_handler(struct uprobe_consumer *self,
+				    struct pt_regs *regs, __u64 *data)
+{
+	struct tlob_uprobe_binding *b =
+		container_of(self, struct tlob_uprobe_binding, stop_probe.uc);
+
+	tlob_stop_task(current, b);
+	return 0;
+}
+
+static void tlob_unbind_quiesce(struct tlob_uprobe_binding *b);
+static void tlob_unbind_reap(struct tlob_uprobe_binding *b);
+
+/*
+ * tlob_unbind_teardown - unregister @b's probes, reap the windows they opened
+ * and drop @b's inode/path references.  @b must already be off
+ * tlob_uprobe_list; the caller frees it.
+ */
+static void tlob_unbind_teardown(struct tlob_uprobe_binding *b)
+{
+	rv_uprobe_unregister_nosync(&b->start_probe);
+	rv_uprobe_unregister_nosync(&b->stop_probe);
+
+	tlob_unbind_quiesce(b);
+	rv_uprobe_unregister_sync();
+	tlob_unbind_reap(b);
+
+	iput(b->start_probe.inode);
+	iput(b->stop_probe.inode);
+	path_put(&b->path);
+}
+
+static int tlob_add_uprobe(const char *name, const char *binpath,
+			   loff_t offset_start, loff_t offset_stop,
+			   u64 threshold_ns)
+{
+	struct tlob_uprobe_binding *tmp_b;
+	char pathbuf[TLOB_MAX_PATH];
+	struct inode *inode;
+	struct path path __free(path_put) = {};
+	char *canon;
+	int ret;
+
+	guard(mutex)(&tlob_uprobe_mutex);
+
+	if (binpath[0] != '/')
+		return -EINVAL;
+
+	struct tlob_uprobe_binding *b __free(kfree) =
+		kzalloc_obj(*b, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	strscpy(b->name, name, sizeof(b->name));
+	b->threshold_ns = threshold_ns;
+	b->offset_start = offset_start;
+	b->offset_stop  = offset_stop;
+	INIT_LIST_HEAD(&b->started_list);
+	INIT_LIST_HEAD(&b->quiesced_list);
+
+	ret = kern_path(binpath, LOOKUP_FOLLOW, &path);
+	if (ret)
+		return ret;
+
+	if (!d_is_reg(path.dentry))
+		return -EINVAL;
+
+	inode = d_real_inode(path.dentry);
+
+	/*
+	 * Names are unique, and so are ranges on the same binary: a task
+	 * runs one window at a time, the second would silently shadow the
+	 * first.  Direction normalised so a reversed start/stop still
+	 * matches.
+	 */
+	list_for_each_entry(tmp_b, &tlob_uprobe_list, list) {
+		loff_t s1, e1, s2, e2;
+
+		if (strcmp(tmp_b->name, name) == 0)
+			return -EEXIST;
+		if (!rv_uprobe_is_registered(&tmp_b->start_probe))
+			continue;
+		if (tmp_b->start_probe.inode != inode)
+			continue;
+
+		s1 = min(offset_start, offset_stop);
+		e1 = max(offset_start, offset_stop);
+		s2 = min(tmp_b->offset_start, tmp_b->offset_stop);
+		e2 = max(tmp_b->offset_start, tmp_b->offset_stop);
+		if (s1 <= e2 && s2 <= e1)
+			return -EEXIST;
+	}
+
+	canon = d_path(&path, pathbuf, sizeof(pathbuf));
+	if (IS_ERR(canon))
+		return PTR_ERR(canon);
+	strscpy(b->binpath, canon, sizeof(b->binpath));
+
+	b->start_probe.uc.handler = tlob_uprobe_entry_handler;
+	ret = rv_uprobe_register(inode, offset_start, &b->start_probe);
+	if (ret)
+		return ret;
+
+	b->stop_probe.uc.handler = tlob_uprobe_stop_handler;
+	/* Both probes pin the one inode resolved above: same binary. */
+	ret = rv_uprobe_register(inode, offset_stop, &b->stop_probe);
+	if (ret) {
+		/*
+		 * The start probe was live: tear down the windows it opened
+		 * before @b goes away, in the same order as the remove path.
+		 */
+		tlob_unbind_teardown(b);
+		return ret;
+	}
+
+	b->path = path;
+	path_get(&b->path);
+
+	list_add_tail(&no_free_ptr(b)->list, &tlob_uprobe_list);
+	return 0;
+}
+
+/*
+ * tlob_unbind_quiesce - claim @b's windows with TLOB_RETIRED and move them to
+ * @b->quiesced_list.  The claim keeps the record from restarting over the
+ * uprobe barrier: a straggler START fails with -ESRCH, no timer is re-armed
+ * (the record is no longer OPEN), and timer expiry finds the window already
+ * closed.
+ */
+static void tlob_unbind_quiesce(struct tlob_uprobe_binding *b)
+{
+	struct tlob_record *rec, *tmp;
+
+	scoped_guard(spinlock, &tlob_binding_lock) {
+		list_for_each_entry_safe(rec, tmp, &b->started_list,
+					 started_node) {
+			list_del_init(&rec->started_node);
+			rec->binding = NULL;
+			/* Already claimed: the exit path owns the free. */
+			if (atomic_xchg(&rec->state, TLOB_RETIRED) == TLOB_RETIRED)
+				continue;
+			list_add_tail(&rec->started_node, &b->quiesced_list);
+		}
+	}
+}
+
+/*
+ * tlob_unbind_reap - destroy @b's windows after the uprobe barrier.  Quiescing
+ * again claims whatever an in-flight handler started past the first pass; the
+ * TLOB_RETIRED claim is what keeps every record here alive until it is destroyed.
+ */
+static void tlob_unbind_reap(struct tlob_uprobe_binding *b)
+{
+	struct tlob_record *rec, *tmp;
+	LIST_HEAD(claimed);
+
+	tlob_unbind_quiesce(b);
+
+	scoped_guard(spinlock, &tlob_binding_lock)
+		list_splice_tail_init(&b->quiesced_list, &claimed);
+
+	list_for_each_entry_safe(rec, tmp, &claimed, started_node) {
+		list_del_init(&rec->started_node);
+		tlob_destroy_claimed(rec);
+	}
+}
+
+static int tlob_remove_uprobe_by_name(const char *name)
+{
+	struct tlob_uprobe_binding *b, *tmp;
+
+	guard(mutex)(&tlob_uprobe_mutex);
+
+	list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
+		if (strcmp(b->name, name))
+			continue;
+		list_del(&b->list);
+		/* Sync may sleep; list_del() already hid the binding. */
+		tlob_unbind_teardown(b);
+		kfree(b);
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static void tlob_remove_all_uprobes(void)
+{
+	struct tlob_uprobe_binding *b, *tmp;
+	LIST_HEAD(pending);
+
+	/*
+	 * Held across the sync and the reap: dropping it here would let a
+	 * concurrent disable tear the pools down under the reap.
+	 */
+	guard(mutex)(&tlob_uprobe_mutex);
+
+	list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
+		list_move(&b->list, &pending);
+		rv_uprobe_unregister_nosync(&b->start_probe);
+		rv_uprobe_unregister_nosync(&b->stop_probe);
+		tlob_unbind_quiesce(b);
+	}
+
+	if (list_empty(&pending))
+		return;
+
+	/* One sync covers all dequeued probes. */
+	rv_uprobe_unregister_sync();
+
+	list_for_each_entry_safe(b, tmp, &pending, list) {
+		list_del(&b->list);
+		tlob_unbind_reap(b);
+		iput(b->start_probe.inode);
+		iput(b->stop_probe.inode);
+		path_put(&b->path);
+		kfree(b);
+	}
+}
+
+static void *tlob_seq_start(struct seq_file *m, loff_t *pos)
+{
+	mutex_lock(&tlob_uprobe_mutex);
+	return seq_list_start(&tlob_uprobe_list, *pos);
+}
+
+static void *tlob_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	return seq_list_next(v, &tlob_uprobe_list, pos);
+}
+
+static void tlob_seq_stop(struct seq_file *m, void *v)
+{
+	mutex_unlock(&tlob_uprobe_mutex);
+}
+
+static int tlob_seq_show(struct seq_file *m, void *v)
+{
+	struct tlob_uprobe_binding *b =
+		list_entry(v, struct tlob_uprobe_binding, list);
+
+	seq_printf(m, "p:%s %s:0x%llx 0x%llx threshold=%llu\n",
+		   b->name, b->binpath,
+		   (unsigned long long)b->offset_start,
+		   (unsigned long long)b->offset_stop,
+		   b->threshold_ns);
+	return 0;
+}
+
+static const struct seq_operations tlob_monitor_seq_ops = {
+	.start	= tlob_seq_start,
+	.next	= tlob_seq_next,
+	.stop	= tlob_seq_stop,
+	.show	= tlob_seq_show,
+};
+
+/*
+ * Parse "p:NAME PATH:OFFSET_START OFFSET_STOP threshold=NS"; @buf is modified
+ * and the outputs point into it.
+ *
+ * PATH may contain ':', so the last one separates it from OFFSET_START.  The
+ * offsets are roles, not a range: STOP may sit at a lower offset than START.
+ */
+static int tlob_parse_uprobe_line(char *buf, char **name_out, char **path_out,
+				  loff_t *start_out, loff_t *stop_out,
+				  u64 *thr_out)
+{
+	unsigned long long thr = 0, stop_val = 0;
+	long long start_val;
+	char *p, *name, *path_token, *token, *colon;
+	bool got_stop = false, got_thr = false;
+
+	if (buf[0] != 'p' || buf[1] != ':')
+		return -EINVAL;
+
+	p = buf + 2;
+	while (*p == ' ' || *p == '\t')
+		p++;
+
+	name = strsep(&p, " \t");
+	if (strlen(name) >= TLOB_MAX_NAME || !is_good_name(name))
+		return -EINVAL;
+
+	if (!p)
+		return -EINVAL;
+	while (*p == ' ' || *p == '\t')
+		p++;
+	path_token = strsep(&p, " \t");
+	if (!path_token || !*path_token)
+		return -EINVAL;
+
+	colon = strrchr(path_token, ':');
+	if (!colon || colon - path_token < 2)
+		return -EINVAL;
+	*colon = '\0';
+
+	if (path_token[0] != '/')
+		return -EINVAL;
+
+	if (kstrtoll(colon + 1, 0, &start_val))
+		return -EINVAL;
+	if (start_val < 0)
+		return -EINVAL;
+
+	while (p && (token = strsep(&p, " \t")) != NULL) {
+		if (!*token)
+			continue;
+		if (strncmp(token, "threshold=", 10) == 0) {
+			if (got_thr)
+				return -EINVAL;
+			if (kstrtoull(token + 10, 0, &thr))
+				return -EINVAL;
+			if (thr < TLOB_MIN_THRESHOLD_NS ||
+			    thr > TLOB_MAX_THRESHOLD_NS)
+				return -ERANGE;
+			got_thr = true;
+		} else if (!got_stop) {
+			long long sv;
+
+			if (kstrtoll(token, 0, &sv))
+				return -EINVAL;
+			if (sv < 0)
+				return -EINVAL;
+			stop_val = (unsigned long long)sv;
+			got_stop = true;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	if (!got_stop || !got_thr)
+		return -EINVAL;
+	if (start_val == (long long)stop_val)
+		return -EINVAL;
+
+	*name_out  = name;
+	*thr_out   = thr;
+	*path_out  = path_token;
+	*start_out = (loff_t)start_val;
+	*stop_out  = (loff_t)stop_val;
+	return 0;
+}
+
+/*
+ * Parse "-:NAME" (ftrace uprobe_events removal convention).
+ */
+static int tlob_parse_remove_line(char *buf, char **name_out)
+{
+	char *name = buf + 2;
+
+	if (buf[0] != '-' || buf[1] != ':')
+		return -EINVAL;
+	if (strlen(name) >= TLOB_MAX_NAME || !is_good_name(name))
+		return -EINVAL;
+
+	*name_out = name;
+	return 0;
+}
+
+static int tlob_create_or_delete_uprobe(char *buf)
+{
+	loff_t offset_start, offset_stop;
+	u64 threshold_ns;
+	char *name, *binpath;
+	int ret;
+
+	if (buf[0] == '-' && buf[1] == ':') {
+		ret = tlob_parse_remove_line(buf, &name);
+		if (ret)
+			return ret;
+		return tlob_remove_uprobe_by_name(name);
+	}
+	ret = tlob_parse_uprobe_line(buf, &name, &binpath, &offset_start,
+				     &offset_stop, &threshold_ns);
+	if (ret)
+		return ret;
+	return tlob_add_uprobe(name, binpath, offset_start, offset_stop,
+			       threshold_ns);
+}
+
+static ssize_t tlob_monitor_write(struct file *file,
+				  const char __user *ubuf,
+				  size_t count, loff_t *ppos)
+{
+	char buf[TLOB_MAX_PATH + TLOB_MAX_NAME + 128];
+
+	if (count >= sizeof(buf))
+		return -EINVAL;
+	if (copy_from_user(buf, ubuf, count))
+		return -EFAULT;
+	buf[count] = '\0';
+	if (count > 0 && buf[count - 1] == '\n')
+		buf[count - 1] = '\0';
+	/* Empty line: keep 'echo > monitor' working after the O_TRUNC clear. */
+	if (!buf[0])
+		return count;
+	return tlob_create_or_delete_uprobe(buf) ?: (ssize_t)count;
+}
+
+/*
+ * ftrace dynamic-event convention: opening for write with O_TRUNC ('>'
+ * redirection) clears all bindings; individual commands, including
+ * removals, are appended with '>>'.
+ */
+static int tlob_monitor_open(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	ret = security_locked_down(LOCKDOWN_TRACEFS);
+	if (ret)
+		return ret;
+
+	if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC))
+		tlob_remove_all_uprobes();
+
+	return seq_open(file, &tlob_monitor_seq_ops);
+}
+
+static const struct file_operations tlob_monitor_fops = {
+	.open		= tlob_monitor_open,
+	.read		= seq_read,
+	.write		= tlob_monitor_write,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+static int __tlob_init_monitor(void)
+{
+	int retval;
+
+	retval = mempool_init_kmalloc_pool(&tlob_rec_pool, TLOB_MAX_MONITORED,
+					   sizeof(struct tlob_record));
+	if (retval)
+		return retval;
+
+	retval = ha_monitor_init();
+	if (retval) {
+		mempool_exit(&tlob_rec_pool);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __tlob_destroy_monitor(void)
+{
+	rv_this.enabled = 0;
+	tlob_remove_all_uprobes();
+	/*
+	 * A grace period only makes the call_rcu()'d tlob_rec_return_cb()
+	 * callbacks eligible to run; rcu_barrier() waits until they have all
+	 * returned their slots before the pool is destroyed.
+	 */
+	ha_monitor_destroy();
+	rcu_barrier();
+	mempool_exit(&tlob_rec_pool);
+}
+
+static int tlob_enable_hooks(void)
+{
+	rv_attach_trace_probe("tlob", sched_switch, handle_sched_switch);
+	rv_attach_trace_probe("tlob", sched_wakeup, handle_sched_wakeup);
+	rv_attach_trace_probe("tlob", sched_process_exit,
+			      handle_sched_process_exit);
+	return 0;
+}
+
+static void tlob_disable_hooks(void)
+{
+	rv_detach_trace_probe("tlob", sched_switch, handle_sched_switch);
+	rv_detach_trace_probe("tlob", sched_wakeup, handle_sched_wakeup);
+	rv_detach_trace_probe("tlob", sched_process_exit,
+			      handle_sched_process_exit);
+}
+
+static int enable_tlob(void)
+{
+	int retval;
+
+	retval = __tlob_init_monitor();
+	if (retval)
+		return retval;
+
+	return tlob_enable_hooks();
+}
+
+static void disable_tlob(void)
+{
+	tlob_disable_hooks();
+	__tlob_destroy_monitor();
+}
+
+static struct rv_monitor rv_this = {
+	.name		= "tlob",
+	.description	= "Per-task latency-over-budget monitor.",
+	.enable		= enable_tlob,
+	.disable	= disable_tlob,
+	.reset		= da_monitor_reset_all,
+	.enabled	= 0,
+};
+
+static int __init register_tlob(void)
+{
+	int ret;
+
+	ret = rv_register_monitor(&rv_this, NULL);
+	if (ret)
+		return ret;
+
+	if (rv_this.root_d) {
+		if (!rv_create_file("monitor", RV_MODE_WRITE,
+				    rv_this.root_d, NULL,
+				    &tlob_monitor_fops)) {
+			rv_unregister_monitor(&rv_this);
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static void __exit unregister_tlob(void)
+{
+	rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_tlob);
+module_exit(unregister_tlob);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Wen Yang <wen.yang@linux.dev>");
+MODULE_DESCRIPTION("tlob: task latency over budget per-task monitor.");
diff --git a/kernel/trace/rv/monitors/tlob/tlob.h b/kernel/trace/rv/monitors/tlob/tlob.h
new file mode 100644
index 000000000000..15cfb4c0b1e8
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _RV_TLOB_H
+#define _RV_TLOB_H
+
+/*
+ * C representation of the tlob hybrid automaton (see tlob.dot).
+ * "stop" fires only from running (both callers run on CPU); running x
+ * start is INVALID: a stray re-start must not reset the budget clock.
+ * Invariant: clk_elapsed <= BUDGET_NS() in running/waiting/sleeping;
+ * stopped is exempt.
+ *
+ * Format: Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#include <linux/rv.h>
+#include <linux/sched.h>
+
+#define MONITOR_NAME tlob
+
+enum states_tlob {
+	stopped_tlob,
+	running_tlob,
+	sleeping_tlob,
+	waiting_tlob,
+	state_max_tlob,
+};
+
+#define INVALID_STATE state_max_tlob
+
+enum events_tlob {
+	preempt_tlob,
+	sleep_tlob,
+	start_tlob,
+	stop_tlob,
+	switch_in_tlob,
+	wakeup_tlob,
+	event_max_tlob,
+};
+
+/*
+ * HA clock env: clk_elapsed, elapsed time since the window start; anchored in
+ * running/waiting/sleeping, cleared on stop.
+ */
+enum envs_tlob {
+	clk_elapsed_tlob,
+	env_max_tlob,
+	env_max_stored_tlob = env_max_tlob,
+};
+
+_Static_assert(env_max_stored_tlob <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_tlob {
+	char *state_names[state_max_tlob];
+	char *event_names[event_max_tlob];
+	char *env_names[env_max_tlob];
+	unsigned char function[state_max_tlob][event_max_tlob];
+	unsigned char initial_state;
+	bool final_states[state_max_tlob];
+};
+
+static const struct automaton_tlob automaton_tlob = {
+	.state_names = {
+		"stopped",
+		"running",
+		"sleeping",
+		"waiting",
+	},
+	.event_names = {
+		"preempt",
+		"sleep",
+		"start",
+		"stop",
+		"switch_in",
+		"wakeup",
+	},
+	.env_names = {
+		"clk_elapsed",
+	},
+	.function = {
+		/* stopped: sched events self-loop */
+		{
+			stopped_tlob,	/* preempt   */
+			stopped_tlob,	/* sleep     */
+			running_tlob,	/* start     */
+			INVALID_STATE,	/* stop      */
+			stopped_tlob,	/* switch_in */
+			stopped_tlob,	/* wakeup    */
+		},
+		/* running */
+		{
+			waiting_tlob,	/* preempt (prev_state == 0)  */
+			sleeping_tlob,	/* sleep (prev_state != 0)    */
+			INVALID_STATE,	/* start (is -EALREADY)       */
+			stopped_tlob,	/* stop                       */
+			INVALID_STATE,	/* switch_in                  */
+			running_tlob,	/* wakeup                     */
+		},
+		/* sleeping */
+		{
+			INVALID_STATE,	/* preempt   */
+			INVALID_STATE,	/* sleep     */
+			INVALID_STATE,	/* start     */
+			INVALID_STATE,	/* stop      */
+			INVALID_STATE,	/* switch_in (via waiting) */
+			waiting_tlob,	/* wakeup    */
+		},
+		/* waiting */
+		{
+			INVALID_STATE,	/* preempt   */
+			INVALID_STATE,	/* sleep     */
+			INVALID_STATE,	/* start     */
+			INVALID_STATE,	/* stop      */
+			running_tlob,	/* switch_in */
+			waiting_tlob,	/* wakeup    */
+		},
+	},
+	.initial_state = stopped_tlob,
+	.final_states = { 1, 0, 0, 0 },
+};
+
+/*
+ * Hard cap on concurrently monitored tasks.  tlob_ws_pool pre-allocates
+ * this many slots; a fresh start past the cap returns -ENOSPC with bounded
+ * latency (mempool_alloc_preallocated() never touches the allocator).
+ * Restarts reuse the same slot.
+ */
+#define TLOB_MAX_MONITORED	64U
+
+/* Maximum binary path length for uprobe binding. */
+#define TLOB_MAX_PATH		256
+
+/* Maximum binding name length (excluding NUL). */
+#define TLOB_MAX_NAME		32
+
+/* Minimum monitoring budget (1 us). */
+#define TLOB_MIN_THRESHOLD_NS	1000ULL
+
+/* Upper budget bound (1 hour). */
+#define TLOB_MAX_THRESHOLD_NS	3600000000000ULL
+
+#endif /* _RV_TLOB_H */
diff --git a/kernel/trace/rv/monitors/tlob/tlob_trace.h b/kernel/trace/rv/monitors/tlob/tlob_trace.h
new file mode 100644
index 000000000000..cda3f39fc110
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob_trace.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TLOB
+DEFINE_EVENT(event_da_monitor_id, event_tlob,
+	     TP_PROTO(int id, char *state, char *event,
+		      char *next_state, bool final_state),
+	     TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_tlob,
+	     TP_PROTO(int id, char *state, char *event),
+	     TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_tlob,
+	     TP_PROTO(int id, char *state, char *event, char *env),
+	     TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TLOB */
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 2f8a932432c9..4bfa39717cef 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -189,6 +189,7 @@ DECLARE_EVENT_CLASS(error_env_da_monitor_id,
 
 #include <monitors/stall/stall_trace.h>
 #include <monitors/nomiss/nomiss_trace.h>
+#include <monitors/tlob/tlob_trace.h>
 // Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
 
 #endif
-- 
2.25.1


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

* [PATCH v7 12/14] rv: Add KUnit tests for the tlob monitor
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (10 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 11/14] rv: Add tlob hybrid automaton monitor wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 13/14] selftests/verification: Add tlob selftests wen.yang
  2026-10-11 19:39 ` [PATCH v7 14/14] selftests/ftrace: Walk up parent dirs to find test.d/functions wen.yang
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

Add a test case to the shared rv_monitors_test.c suite covering
the uprobe-line parser in the tlob monitor.

Cases cover valid inputs, malformed paths and offsets (including
negative values), out-of-range thresholds, and valid and invalid
remove lines.

Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 kernel/trace/rv/monitors/tlob/tlob.c       |  11 ++
 kernel/trace/rv/monitors/tlob/tlob_kunit.c | 151 +++++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob_kunit.h |  18 +++
 kernel/trace/rv/rv_monitors_test.c         |   2 +
 4 files changed, 182 insertions(+)
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.h

diff --git a/kernel/trace/rv/monitors/tlob/tlob.c b/kernel/trace/rv/monitors/tlob/tlob.c
index 066a2f0663d5..159fc9a2c49e 100644
--- a/kernel/trace/rv/monitors/tlob/tlob.c
+++ b/kernel/trace/rv/monitors/tlob/tlob.c
@@ -863,6 +863,17 @@ static int tlob_parse_remove_line(char *buf, char **name_out)
 	return 0;
 }
 
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "tlob_kunit.h"
+
+const struct rv_tlob_kunit_ops rv_tlob_kunit_ops = {
+	.parse_uprobe_line = tlob_parse_uprobe_line,
+	.parse_remove_line = tlob_parse_remove_line,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_tlob_kunit_ops);
+#endif
+
 static int tlob_create_or_delete_uprobe(char *buf)
 {
 	loff_t offset_start, offset_stop;
diff --git a/kernel/trace/rv/monitors/tlob/tlob_kunit.c b/kernel/trace/rv/monitors/tlob/tlob_kunit.c
new file mode 100644
index 000000000000..06b212d1103b
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob_kunit.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include "tlob_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TLOB)
+
+/* Valid "p:NAME PATH:START STOP threshold=NS" lines and what they decode to. */
+static const struct {
+	const char	*line;
+	const char	*name;
+	const char	*path;
+	loff_t		start;
+	loff_t		stop;
+	u64		thr;
+} tlob_parse_valid[] = {
+	{ "p:region /usr/bin/myapp:4768 4848 threshold=5000000",
+	  "region", "/usr/bin/myapp", 4768, 4848, 5000000 },
+	/* Base 0: hex offsets must not decode as decimal. */
+	{ "p:my_region_2 /usr/bin/myapp:0x12a0 0x12f0 threshold=10000000",
+	  "my_region_2", "/usr/bin/myapp", 0x12a0, 0x12f0, 10000000 },
+	/* The last ':' separates the offset, so a ':' in PATH is fine. */
+	{ "p:_r3 /opt/my:app/bin:0x100 0x200 threshold=1000000",
+	  "_r3", "/opt/my:app/bin", 0x100, 0x200, 1000000 },
+	/* TLOB_MAX_NAME - 1 characters: the longest name accepted. */
+	{ "p:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /bin/a:0x100 0x200 threshold=5000",
+	  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "/bin/a", 0x100, 0x200, 5000 },
+	/* The accepted threshold bounds themselves. */
+	{ "p:lo /bin/a:0x100 0x200 threshold=1000",
+	  "lo", "/bin/a", 0x100, 0x200, 1000 },
+	{ "p:hi /bin/a:0x100 0x200 threshold=3600000000000",
+	  "hi", "/bin/a", 0x100, 0x200, 3600000000000ULL },
+};
+
+/* Malformed "p:..." lines that must be rejected with -EINVAL. */
+static const char * const tlob_parse_invalid[] = {
+	"p /usr/bin/myapp:0x100 0x200 threshold=5000",		/* no name */
+	"p:",							/* empty name */
+	"p: /usr/bin/myapp:0x100 0x200 threshold=5000",		/* name is a path */
+	"p:r /bin/a:0x100junk 0x200 threshold=5000",		/* trailing junk */
+	"p:1region /usr/bin/myapp:0x100 0x200 threshold=5000",	/* digit lead */
+	"p:my-win /usr/bin/myapp:0x100 0x200 threshold=5000",	/* bad char */
+	"p:my.win /usr/bin/myapp:0x100 0x200 threshold=5000",	/* bad char */
+	/* TLOB_MAX_NAME characters: one over the limit. */
+	"p:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /bin/a:0x100 0x200 threshold=5000",
+	"p:region :0x100 0x200 threshold=5000",			/* empty path */
+	"p:region /usr/bin/myapp:0x100 threshold=5000",		/* missing stop */
+	"p:region /usr/bin/myapp:-1 0x200 threshold=5000",		/* negative start */
+	"p:region /usr/bin/myapp:0x100 -1 threshold=5000000",	/* negative stop */
+	"p:region /usr/bin/myapp:0x100 0x200",			/* missing threshold */
+	"p:region /usr/bin/myapp:0x100 0x100 threshold=5000",	/* start == stop */
+	/* Duplicate threshold=, like a duplicate offset, is an error. */
+	"p:region /bin/a:0x100 0x200 threshold=5000 threshold=9000",
+};
+
+/* threshold_ns out of valid range => -ERANGE. */
+static const char * const tlob_parse_out_of_range[] = {
+	"p:region /usr/bin/myapp:0x100 0x200 threshold=0",
+	"p:region /usr/bin/myapp:0x100 0x200 threshold=999",
+	"p:region /usr/bin/myapp:0x100 0x200 threshold=3600000000001",
+};
+
+/* Valid "-:NAME" remove lines. */
+static const char * const tlob_remove_valid[] = {
+	"-:region",
+	"-:my_region",
+};
+
+/* Malformed remove lines that must be rejected with -EINVAL. */
+static const char * const tlob_remove_invalid[] = {
+	"-",	/* no name */
+	"-:",	/* empty name */
+	"-region",	/* missing ':' */
+	"-:my region",	/* bad char */
+	"-:1region",	/* digit lead */
+	"-:my-region",	/* bad char */
+	"-:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",	/* TLOB_MAX_NAME, one too many */
+};
+
+static void rv_test_tlob(struct kunit *test)
+{
+	u64 thr;
+	char *name, *path;
+	loff_t start, stop;
+	char buf[128];
+	int i, ret;
+
+	/* NAME and PATH come back NUL-terminated inside buf. */
+	strscpy(buf, "p:region /usr/bin/myapp:0x100 0x200 threshold=5000",
+		sizeof(buf));
+	ret = rv_tlob_kunit_ops.parse_uprobe_line(buf, &name, &path,
+						  &start, &stop, &thr);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_STREQ(test, name, "region");
+	KUNIT_EXPECT_STREQ(test, path, "/usr/bin/myapp");
+
+	strscpy(buf, "-:region", sizeof(buf));
+	ret = rv_tlob_kunit_ops.parse_remove_line(buf, &name);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_STREQ(test, name, "region");
+
+	for (i = 0; i < ARRAY_SIZE(tlob_parse_valid); i++) {
+		strscpy(buf, tlob_parse_valid[i].line, sizeof(buf));
+		ret = rv_tlob_kunit_ops.parse_uprobe_line(buf, &name, &path,
+							  &start, &stop,
+							  &thr);
+		KUNIT_EXPECT_EQ(test, ret, 0);
+		if (ret)
+			continue;
+		KUNIT_EXPECT_STREQ(test, name, tlob_parse_valid[i].name);
+		KUNIT_EXPECT_STREQ(test, path, tlob_parse_valid[i].path);
+		KUNIT_EXPECT_EQ(test, start, tlob_parse_valid[i].start);
+		KUNIT_EXPECT_EQ(test, stop, tlob_parse_valid[i].stop);
+		KUNIT_EXPECT_EQ(test, thr, tlob_parse_valid[i].thr);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tlob_parse_invalid); i++) {
+		strscpy(buf, tlob_parse_invalid[i], sizeof(buf));
+		ret = rv_tlob_kunit_ops.parse_uprobe_line(buf, &name, &path,
+							  &start, &stop,
+							  &thr);
+		KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tlob_parse_out_of_range); i++) {
+		strscpy(buf, tlob_parse_out_of_range[i], sizeof(buf));
+		ret = rv_tlob_kunit_ops.parse_uprobe_line(buf, &name, &path,
+							  &start, &stop,
+							  &thr);
+		KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tlob_remove_valid); i++) {
+		strscpy(buf, tlob_remove_valid[i], sizeof(buf));
+		ret = rv_tlob_kunit_ops.parse_remove_line(buf, &name);
+		KUNIT_EXPECT_EQ(test, ret, 0);
+		if (!ret)
+			KUNIT_EXPECT_STREQ(test, name,
+					   tlob_remove_valid[i] + 2);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tlob_remove_invalid); i++) {
+		strscpy(buf, tlob_remove_invalid[i], sizeof(buf));
+		ret = rv_tlob_kunit_ops.parse_remove_line(buf, &name);
+		KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+	}
+}
+
+#else
+#define rv_test_tlob rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/tlob/tlob_kunit.h b/kernel/trace/rv/monitors/tlob/tlob_kunit.h
new file mode 100644
index 000000000000..c0c80c34f17b
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob_kunit.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __TLOB_KUNIT_H
+#define __TLOB_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/types.h>
+
+extern const struct rv_tlob_kunit_ops {
+	int (*parse_uprobe_line)(char *buf, char **name_out, char **path_out,
+				 loff_t *start_out, loff_t *stop_out,
+				 u64 *thr_out);
+	int (*parse_remove_line)(char *buf, char **name_out);
+} rv_tlob_kunit_ops;
+
+#endif
+
+#endif /* __TLOB_KUNIT_H */
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index b30e49419e34..ee9fde63b291 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -309,6 +309,7 @@ static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
 #include "monitors/sts/sts_kunit.c"
 #include "monitors/opid/opid_kunit.c"
 #include "monitors/nomiss/nomiss_kunit.c"
+#include "monitors/tlob/tlob_kunit.c"
 #include "monitors/pagefault/pagefault_kunit.c"
 #include "monitors/sleep/sleep_kunit.c"
 
@@ -323,6 +324,7 @@ static struct kunit_case rv_mon_test_cases[] = {
 	KUNIT_CASE(rv_test_sleep),
 	KUNIT_CASE(rv_test_rvtest_underflow),
 	KUNIT_CASE(rv_test_rvtest_arm_after_destroy),
+	KUNIT_CASE(rv_test_tlob),
 	{}
 };
 
-- 
2.25.1


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

* [PATCH v7 13/14] selftests/verification: Add tlob selftests
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (11 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 12/14] rv: Add KUnit tests for the tlob monitor wen.yang
@ 2026-10-11 19:39 ` wen.yang
  2026-10-11 19:39 ` [PATCH v7 14/14] selftests/ftrace: Walk up parent dirs to find test.d/functions wen.yang
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

Add ten ftrace-style test scripts for the tlob RV monitor under
tools/testing/selftests/verification/test.d/tlob/.  The tests cover
uprobe binding management, budget violation detection, multi-binding,
multi-task sharing of one binding, overlap registration rejection,
unbind and disable on live tasks, and start/stop restart.

Helper binarie tlob_target is built via TEST_GEN_FILES, following
the kselftests convention.  run_tlob_tests.sh builds the helpers
and delegates argument handling to ftracetest.

Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 .../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_breakdown.tc           | 136 ++++++++++++++++++
 .../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_no_event.tc            |  17 +++
 .../test.d/tlob/uprobe_overlap_register.tc    |  66 +++++++++
 .../test.d/tlob/uprobe_restart.tc             |  66 +++++++++
 .../test.d/tlob/uprobe_restart_violation.tc   |  48 +++++++
 .../test.d/tlob/uprobe_unbind_live.tc         |  65 +++++++++
 .../test.d/tlob/uprobe_violation.tc           |  53 +++++++
 .../selftests/verification/tlob_target.c      | 127 ++++++++++++++++
 17 files changed, 876 insertions(+)
 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_breakdown.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_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_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_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

diff --git a/tools/testing/selftests/verification/.gitignore b/tools/testing/selftests/verification/.gitignore
index 2659417cb2c7..92d7ebdb1dd1 100644
--- a/tools/testing/selftests/verification/.gitignore
+++ b/tools/testing/selftests/verification/.gitignore
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 logs
+tlob_target
diff --git a/tools/testing/selftests/verification/Makefile b/tools/testing/selftests/verification/Makefile
index aa8790c22a71..dcdfe3ea3c06 100644
--- a/tools/testing/selftests/verification/Makefile
+++ b/tools/testing/selftests/verification/Makefile
@@ -5,4 +5,8 @@ TEST_PROGS := verificationtest-ktap
 TEST_FILES := test.d settings
 EXTRA_CLEAN := $(OUTPUT)/logs/*
 
+TEST_GEN_FILES := tlob_target
+
 include ../lib.mk
+
+export VERIFICATIONTEST_BINDIR := $(OUTPUT)
diff --git a/tools/testing/selftests/verification/config b/tools/testing/selftests/verification/config
index 43072c1c38f4..5869ae4a9217 100644
--- a/tools/testing/selftests/verification/config
+++ b/tools/testing/selftests/verification/config
@@ -1 +1,4 @@
 CONFIG_RV=y
+CONFIG_RV_MON_TLOB=y
+CONFIG_UPROBES=y
+CONFIG_HIGH_RES_TIMERS=y
diff --git a/tools/testing/selftests/verification/test.d/functions b/tools/testing/selftests/verification/test.d/functions
index ec36a092f56e..43c119682c1f 100644
--- a/tools/testing/selftests/verification/test.d/functions
+++ b/tools/testing/selftests/verification/test.d/functions
@@ -25,6 +25,15 @@ check_requires() { # Check required files, monitors and reactors
 	done
 }
 
+sym_offset() { # <binary> <symbol>: print the ELF file offset of <symbol>
+	readelf -W -S -s "$1" | awk -v symbol="$2" '
+		{ gsub(/\[ /, "[") }
+		$1 ~ /^\[[0-9]+\]$/ { sections[$1]="0x"$4; offsets[$1]="0x"$5 }
+		$1 ~ /^[0-9]+:$/ && $NF == symbol { addr="0x"$2; sec="["$7"]" }
+		END { printf "printf \"0x%%x\\n\" $((%s - %s + %s))\n", addr, sections[sec], offsets[sec] }
+	' | sh
+}
+
 initialize_system() { # Reset RV to initial-state
 	echo > enabled_monitors
 	for m in monitors/*; do
diff --git a/tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh b/tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
new file mode 100755
index 000000000000..c87484813fc8
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+#
+# Standalone runner for tlob selftests
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+BINDIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
+FTRACETEST="$(cd "$SCRIPT_DIR/../../../ftrace" && pwd)/ftracetest"
+
+# The helper is built on the host by the selftests Makefile
+# (TEST_GEN_FILES in $BINDIR/Makefile); the VM (virtme-ng) mounts the
+# source read-only and may lack a compiler, so this runner never builds.
+# Build it on the host first:
+#   make -C "$BINDIR" tlob_target
+if [ ! -x "$BINDIR/tlob_target" ]; then
+	echo "ERROR: tlob_target not built in $BINDIR" >&2
+	echo "       build it on the host: make -C $BINDIR tlob_target" >&2
+	exit 1
+fi
+
+# Flag (not fail) the helper older than its source: git checkouts can bump
+# source mtimes, so a hard check would misfire; a host rebuild picks up
+# the new code.
+if [ -f "$BINDIR/tlob_target.c" ] &&
+   [ "$BINDIR/tlob_target" -ot "$BINDIR/tlob_target.c" ]; then
+	echo "WARNING: $BINDIR/tlob_target is older than $BINDIR/tlob_target.c;" >&2
+	echo "         rebuild on the host to test the current source" >&2
+fi
+
+export VERIFICATIONTEST_BINDIR="$BINDIR"
+
+exec "$FTRACETEST" -K --rv "$SCRIPT_DIR" "$@"
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
new file mode 100644
index 000000000000..6484f37d8d2e
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
@@ -0,0 +1,47 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor uprobe binding (named, listed, removable, duplicate name/range rejected, O_TRUNC clears)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+
+"$UPROBE_TARGET" 30000 &
+busy_pid=$!
+teardown() {
+    kill "$busy_pid" 2>/dev/null || true; wait "$busy_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > monitors/tlob/enable
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=5000000000" >> "$TLOB_MONITOR"
+
+# Binding must appear in monitor file with canonical hex-offset format.
+grep -qE "^p:busy_work ${UPROBE_TARGET}:0x[0-9a-f]+ 0x[0-9a-f]+ threshold=[0-9]+$" "$TLOB_MONITOR"
+grep -q "threshold=5000000000" "$TLOB_MONITOR"
+
+# Duplicate range must be rejected.
+! echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=9999000" >> "$TLOB_MONITOR" 2>/dev/null || false
+
+# Duplicate name on a disjoint range must be rejected too.
+! echo "p:busy_work ${UPROBE_TARGET}:$((stop_offset + 64)) $((stop_offset + 128)) threshold=5000000" >> "$TLOB_MONITOR" 2>/dev/null || false
+
+# Remove the binding; it must no longer appear.
+echo "-:busy_work" >> "$TLOB_MONITOR"
+! grep -q "^p:busy_work " "$TLOB_MONITOR" || false
+
+# '>' opens with O_TRUNC and clears all bindings (ftrace dynamic-event
+# convention); individual commands must use '>>'.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=5000000000" >> "$TLOB_MONITOR"
+echo > "$TLOB_MONITOR"
+! grep -q "^p:" "$TLOB_MONITOR" || false
+
+echo 0 > monitors/tlob/enable
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_breakdown.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_breakdown.tc
new file mode 100644
index 000000000000..65f5c61cca23
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_breakdown.tc
@@ -0,0 +1,136 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor per-state breakdown derived from event_tlob
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+# Per-state breakdown of one window, as described in monitor_tlob.rst: each
+# interval between two trace lines belongsto the state the eaerlier line
+# enters, and a violation ends the window at error_env_tlob. This test
+# monitors a single task, so the pid is not tracked.
+#
+# Lines look like (trace_clock=mono):
+#   <comm>-<pid> [<cpu>] <flags> <ts>: event_tlob: <pid>: <cur> x <ev> -> <next>
+#   <comm>-<pid> [<cpu>] <flags> <ts>: error_env_tlob: <pid>: ... clk_elapsed=<ns>
+#
+# Prints "<running> <waiting> <sleeping> <clk_elapsed>" in ns for the first
+# violation. With a second argument, each interval is printed first as
+# "<state> <duration_ms> <event>", the summary last.
+
+tlob_breakdown() { # <trace> [detail]
+	awk -v detail="${2:-}" '
+	function interval(state, dur, event) {
+		if (detail)
+			printf "%-9s %8.3f ms  %s\n", state, dur / 1e6, event
+	}
+
+	{
+		# The event name is the field ending in ":", the timestamp
+		# the one before it.
+		for (i = 1; i <= NF; i++)
+			if ($i ~ /_tlob:$/)
+				break
+		if (i > NF)
+			next
+
+		ts = $(i - 1)
+		sub(/:$/, "", ts)
+		now = ts * 1e9                  # ns
+
+		if ($i == "error_env_tlob:") {  # violation closes the window
+			if (!window)
+				next
+			acc[state] += now - last
+			interval(state, now - last, "budget_exceeded")
+			elapsed = $NF
+			sub(/^clk_elapsed=/, "", elapsed)
+			printf "%.0f %.0f %.0f %s\n",
+			       acc["running"], acc["waiting"],
+			       acc["sleeping"], elapsed
+			exit
+		}
+
+		# "event_tlob: <pid>: <cur> x <event> -> <next>"
+		event = $(i + 4)
+		next_state = $(i + 6)
+
+		if (event == "start") {
+			window = 1
+			acc["running"] = acc["waiting"] = acc["sleeping"] = 0
+		} else if (window) {
+			acc[state] += now - last        # the state just left
+			interval(state, now - last, event)
+		}
+		state = next_state
+		last = now
+	}' "$1"
+}
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+sleep_stop=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work_done)
+
+old_clock=$(sed -n 's/.*\[\(.*\)\].*/\1/p' ../trace_clock)
+teardown() {
+        kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true
+        echo "$old_clock" > ../trace_clock
+}
+trap teardown EXIT
+
+# Timestamps of one window come from several CPUs.
+echo mono > ../trace_clock
+echo 1 > ../events/rv/event_tlob/enable
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# Each "seq" iteration busy-spins 200 ms, then sleeps 200 ms.  One window
+# spans both, so a 300 ms budget expires while sleeping, after ~200 ms
+# busy (running, or waiting when preempted) and ~100 ms asleep.
+echo "p:breakdown ${UPROBE_TARGET}:${busy_offset} ${sleep_stop} threshold=300000000" >> "$TLOB_MONITOR"
+"$UPROBE_TARGET" 0 seq 1 &
+pid=$!
+wait "$pid" || true
+
+echo "-:breakdown" >> "$TLOB_MONITOR"
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/event_tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+
+detail=$(tlob_breakdown ../trace detail)
+summary=$(printf '%s\n' "$detail" | tail -n 1)
+first=$(printf '%s\n' "$detail" | head -n 1)
+dump() { echo "===== tlob breakdown =====" >&2; printf '%s\n' "$detail" >&2; }
+
+set -- $summary
+running=$1 waiting=$2 sleeping=$3 elapsed=$4
+echo "running=$running waiting=$waiting sleeping=$sleeping clk_elapsed=$elapsed"
+
+# A window must have been found and closed by a violation.
+[ -n "$elapsed" ] || { echo "no violation window in the trace" >&2; dump; false; }
+
+# The intervals must add up to the monitor clock, which start resets and
+# the violation reports.  Allow 5 ms for tracepoint and timer latency.
+sum=$((running + waiting + sleeping))
+diff=$((sum > elapsed ? sum - elapsed : elapsed - sum))
+[ "$diff" -lt 5000000 ] || { echo "intervals $sum ns vs clk_elapsed $elapsed ns" >&2; dump; false; }
+
+# The first interval is the one start opens, so it is always "running".
+[ "${first%% *}" = "running" ] || { echo "first interval is not running: $first" >&2; dump; false; }
+
+# The window spans a busy phase and a sleep phase: both must be accounted,
+# and the busy phase is the longer one.
+[ "$sleeping" -gt 0 ] || { echo "no sleeping interval recorded" >&2; dump; false; }
+[ $((running + waiting)) -gt "$sleeping" ] || { echo "sleeping dominates: $summary" >&2; dump; false; }
+
+echo > ../trace
+
+
+
+
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc
new file mode 100644
index 000000000000..c997405d75cc
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc
@@ -0,0 +1,79 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor disable with a live window (armed hrtimer cancelled, no post-cycle violation)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+sleep_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work_done)
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 300 ms budget keeps the live window far from expiry at disable; duration 0
+# loops the target so no STOP can fire while the monitor is down.
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${stop_offset} threshold=300000000" >> "$TLOB_MONITOR"
+
+"$UPROBE_TARGET" 0 sleep &
+target_pid=$!
+teardown() {
+    kill "$target_pid" 2>/dev/null || true; wait "$target_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+# Disable with the window open: the armed hrtimer must be cancelled by the
+# disable path, or it fires on freed storage.  Check /proc/timer_list.
+echo 0 > monitors/tlob/enable
+
+if [ ! -r /proc/timer_list ]; then
+    echo "/proc/timer_list unavailable, cannot check for leaked timers" >&2
+    exit_unresolved
+fi
+
+armed=$(grep -c ha_monitor /proc/timer_list || true)
+if [ "$armed" != "0" ]; then
+    echo "===== tlob disable-with-live-window diagnostic =====" >&2
+    echo "$armed armed ha_monitor hrtimer(s) after disable:" >&2
+    grep ha_monitor /proc/timer_list >&2 || true
+    false
+fi
+
+# A leaked timer would fire on freed storage once re-enabled.
+echo 1 > monitors/tlob/enable
+
+# Re-enabled before the 300 ms budget would have elapsed; wait past it.
+sleep 0.5
+
+if grep -q "error_env_tlob" ../trace; then
+    echo "===== tlob disable-with-live-window diagnostic =====" >&2
+    grep "error_env_tlob" ../trace >&2 || true
+    false
+fi
+
+# Disable/enable must not break detection: a 10 us budget fires on the
+# next sleep window.
+echo > ../trace
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${stop_offset} threshold=10000" >> "$TLOB_MONITOR"
+
+found=0; i=0
+while [ "$i" -lt 20 ]; do
+    sleep 0.1
+    grep -q "error_env_tlob" ../trace && { found=1; break; }
+    i=$((i+1))
+done
+
+echo "-:sleep_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > ../events/rv/error_env_tlob/enable
+echo 0 > monitors/tlob/enable
+
+[ "$found" = "1" ]
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
new file mode 100644
index 000000000000..709561ebc94c
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
@@ -0,0 +1,58 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor multiple uprobe bindings (different offsets fire independently)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+busy_stop=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+sleep_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work)
+sleep_stop=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work_done)
+
+"$UPROBE_TARGET" 30000 &       # busy mode: tlob_busy_work fires every 200 ms
+busy_pid=$!
+"$UPROBE_TARGET" 30000 sleep & # sleep mode: tlob_sleep_work fires every 200 ms
+sleep_pid=$!
+teardown() {
+    kill "$sleep_pid" 2>/dev/null || true; wait "$sleep_pid" 2>/dev/null || true
+    kill "$busy_pid"  2>/dev/null || true; wait "$busy_pid"  2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# Binding A: 5 s budget on the busy probe - must not fire in 200 ms loops.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${busy_stop} threshold=5000000000" >> "$TLOB_MONITOR"
+# Binding B: 10 us budget on the sleep probe - fires on first invocation.
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${sleep_stop} threshold=10000" >> "$TLOB_MONITOR"
+
+# Wait up to 2 s for error_env_tlob from binding B.
+found=0; i=0
+while [ "$i" -lt 20 ]; do
+	sleep 0.1
+	grep -q "error_env_tlob" ../trace && { found=1; break; }
+	i=$((i+1))
+done
+
+echo "-:busy_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo "-:sleep_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+
+[ "$found" = "1" ]
+# error_env_tlob payload: clock variable must be present.
+# The event field can be "budget_exceeded" (hrtimer path) or the DA event
+# name ("sleep", "preempt") depending on which fires first; don't constrain it.
+grep "error_env_tlob" ../trace | head -n 1 | grep -q "clk_elapsed="
+
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
new file mode 100644
index 000000000000..238ceed065b0
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
@@ -0,0 +1,65 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor multiple tasks sharing one uprobe binding (concurrent instances)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+UPROBE_COMM=$(basename "$UPROBE_TARGET")
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+busy_stop=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+
+# Start 3 concurrent instances of the target; each one hits the same binding.
+"$UPROBE_TARGET" 30000 &
+pid1=$!
+"$UPROBE_TARGET" 30000 &
+pid2=$!
+"$UPROBE_TARGET" 30000 &
+pid3=$!
+teardown() {
+	kill "$pid1" 2>/dev/null || true; wait "$pid1" 2>/dev/null || true
+	kill "$pid2" 2>/dev/null || true; wait "$pid2" 2>/dev/null || true
+	kill "$pid3" 2>/dev/null || true; wait "$pid3" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > ../events/rv/event_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 5 s budget on the busy probe: no violation expected in 200 ms loops.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${busy_stop} threshold=5000000000" >> "$TLOB_MONITOR"
+
+# Wait up to 2 s for all three pids to register and transition through start.
+# This proves multiple tasks can hit the same uprobe binding concurrently.
+found1=0; found2=0; found3=0
+i=0
+while [ "$i" -lt 20 ]; do
+	sleep 0.1
+	grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid1}" && found1=1
+	grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid2}" && found2=1
+	grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid3}" && found3=1
+	if [ "$found1" = "1" ] && [ "$found2" = "1" ] && [ "$found3" = "1" ]; then
+		break
+	fi
+	i=$((i+1))
+done
+
+[ "$found1" = "1" ]
+[ "$found2" = "1" ]
+[ "$found3" = "1" ]
+
+# Removing the binding while tasks are active must succeed cleanly.
+echo "-:busy_work" >> "$TLOB_MONITOR"
+! grep -q "^p:busy_work " "$TLOB_MONITOR" || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/event_tlob/enable
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
new file mode 100644
index 000000000000..23f8abeca090
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
@@ -0,0 +1,17 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor no spurious events without active uprobe binding
+# requires: tlob:monitor
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+sleep 0.5
+
+! grep -q "error_env_tlob" ../trace || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc
new file mode 100644
index 000000000000..eb3590fa21aa
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc
@@ -0,0 +1,66 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob rejecting overlapping uprobe bindings and reporting shadowing
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+busy_stop=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+sleep_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work)
+sleep_stop=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work_done)
+
+echo 1 > ../events/rv/error_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# Scenario 1: overlapping ranges on the same binary are rejected at
+# registration (a task runs one window at a time; the second would shadow).
+# Covers three flavours: identical, nested, and crossing.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${busy_stop} threshold=1000000000" >> "$TLOB_MONITOR"
+for r in \
+	"${busy_offset} ${busy_stop}" \
+	"$((busy_offset + 8)) ${busy_stop}" \
+	"$((busy_stop - 8)) $((busy_stop + 8))"; do
+	set -- $r
+	if echo "p:dup ${UPROBE_TARGET}:$1 $2 threshold=1000000000" >> "$TLOB_MONITOR" 2>/dev/null; then
+		echo "ERROR: overlapping binding accepted: $r" >&2
+		false
+	fi
+done
+# Only the original binding survives.
+if [ "$(grep -c "^p:busy_work " "$TLOB_MONITOR")" -ne 1 ]; then
+	echo "ERROR: rejected range left a binding behind" >&2
+	false
+fi
+
+# Scenario 2: two disjoint ranges on the same binary are both accepted, but a
+# task running them sequentially is shadowed by the first binding -- reported
+# via error_tlob instead of a silent no-op.
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${sleep_stop} threshold=1000000000" >> "$TLOB_MONITOR"
+"$UPROBE_TARGET" 0 seq 2 &
+tpid=$!
+teardown() {
+	kill "$tpid" 2>/dev/null || true; wait "$tpid" 2>/dev/null || true
+}
+trap teardown EXIT
+wait "$tpid" || true
+
+if ! grep -q "error_tlob" ../trace; then
+	echo "ERROR: no error_tlob for sequentially shadowed binding" >&2
+	echo "--- trace ---" >&2
+	grep "error_tlob\|event_tlob" ../trace >&2 || true
+	false
+fi
+
+echo "-:busy_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo "-:sleep_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/error_tlob/enable
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
new file mode 100644
index 000000000000..1584fc3d84de
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
@@ -0,0 +1,66 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor restarting a parked window on the same task (unbind of a repeatedly-started task works)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+# On failure: dump violations and the target's transitions, to tell a genuine
+# per-window overrun from a broken restart.
+dump_tlob_diag() {
+	TRACE=../trace
+	echo "===== tlob restart diagnostic (pid ${busy_pid}) =====" >&2
+	echo "--- error_env_tlob (violations) ---" >&2
+	grep "error_env_tlob" "$TRACE" >&2 || true
+	echo "--- event_tlob (state transitions for target pid) ---" >&2
+	grep "event_tlob" "$TRACE" | grep ":${busy_pid}:" >&2 || true
+	echo "--- last 50 trace lines (full context) ---" >&2
+	tail -50 "$TRACE" >&2 || true
+}
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+
+# tlob_target busy-loops ~200ms start/stop windows on the same task for
+# 900ms, driving tlob_start_task()'s restart path on every iteration
+# after the first (~4 windows).
+"$UPROBE_TARGET" 900 &
+busy_pid=$!
+teardown() {
+	kill "$busy_pid" 2>/dev/null || true; wait "$busy_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > ../events/rv/event_tlob/enable
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 300ms budget covers one ~200ms window.  A restart that failed to re-anchor
+# the budget clock would trip on the second window; with a correct restart,
+# no window exceeds it.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=300000000" >> "$TLOB_MONITOR"
+
+wait "$busy_pid" || true
+
+if grep -q "error_env_tlob" ../trace; then
+	dump_tlob_diag
+	false
+fi
+
+# The task has exited (tlob_destroy_task() ran via handle_sched_process_exit);
+# removing its now-stale binding must still succeed cleanly.
+echo "-:busy_work" >> "$TLOB_MONITOR"
+! grep -q "^p:busy_work " "$TLOB_MONITOR" || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/event_tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc
new file mode 100644
index 000000000000..dccb4e5baa11
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc
@@ -0,0 +1,48 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor restarting a window after a budget overrun (each later START on the same task starts a fresh window)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+
+# 30 ms budget: every ~200 ms window overruns.  Bind first, clear the trace
+# after, so the target's first window (START soon after launch) is caught.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=30000000" >> "$TLOB_MONITOR"
+echo > ../trace
+
+# 3 iterations (duration arg unused).  Every window overruns; a dropped
+# restart would leave one unmonitored, dropping the count below 3.
+"$UPROBE_TARGET" 0 busy 3 &
+busy_pid=$!
+teardown() {
+    kill "$busy_pid" 2>/dev/null || true; wait "$busy_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+wait "$busy_pid" || true
+
+errors=$(grep -c "error_env_tlob" ../trace || true)
+if [ "$errors" -lt 3 ]; then
+    echo "ERROR: expected >= 3 error_env_tlob (one per window), got $errors" >&2
+    echo "--- error_env_tlob ---" >&2
+    grep "error_env_tlob" ../trace >&2 || true
+    echo "--- event_tlob (state transitions for target pid) ---" >&2
+    grep "event_tlob" ../trace | grep ":${busy_pid}:" >&2 || true
+    false
+fi
+
+echo "-:busy_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc
new file mode 100644
index 000000000000..698bf907f653
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc
@@ -0,0 +1,65 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob unbind with a live window (no post-unbind violation, detection still works)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+sleep_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_sleep_work_done)
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 300 ms budget keeps the live window far from expiry at unbind; duration 0
+# loops the target.
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${stop_offset} threshold=300000000" >> "$TLOB_MONITOR"
+
+"$UPROBE_TARGET" 0 sleep &
+target_pid=$!
+teardown() {
+    kill "$target_pid" 2>/dev/null || true; wait "$target_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+# Unbind while the window is open: the task can no longer reach STOP, so the
+# window must be ended and its timer cancelled before the budget expires.
+echo "-:sleep_work" >> "$TLOB_MONITOR"
+
+# Wait past the 300 ms budget.
+sleep 0.6
+
+if grep -q "error_env_tlob" ../trace; then
+    echo "===== tlob unbind-with-live-window diagnostic =====" >&2
+    grep "error_env_tlob" ../trace >&2 || true
+    false
+fi
+
+# The target survived the unbind (its window was torn down, not the task).
+kill -0 "$target_pid"
+
+# Unbind must not break detection: a 10 us budget fires on the next sleep window.
+echo > ../trace
+echo "p:sleep_work ${UPROBE_TARGET}:${sleep_offset} ${stop_offset} threshold=10000" >> "$TLOB_MONITOR"
+
+found=0; i=0
+while [ "$i" -lt 20 ]; do
+    sleep 0.1
+    grep -q "error_env_tlob" ../trace && { found=1; break; }
+    i=$((i+1))
+done
+
+echo "-:sleep_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > ../events/rv/error_env_tlob/enable
+echo 0 > monitors/tlob/enable
+
+[ "$found" = "1" ]
+echo > ../trace
diff --git a/tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc b/tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
new file mode 100644
index 000000000000..eb4e5c9bd2e3
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
@@ -0,0 +1,53 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor budget violation (error_env_tlob fires with correct fields)
+# requires: tlob:monitor readelf:program
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work)
+stop_offset=$(sym_offset "$UPROBE_TARGET" tlob_busy_work_done)
+
+"$UPROBE_TARGET" 30000 &
+busy_pid=$!
+teardown() {
+    kill "$busy_pid" 2>/dev/null || true; wait "$busy_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 10 us budget - fires almost immediately; task is busy-spinning on-CPU.
+echo "p:busy_work ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=10000" >> "$TLOB_MONITOR"
+
+# wait up to 2 s for error_env_tlob
+found=0; i=0
+while [ "$i" -lt 20 ]; do
+	sleep 0.1
+	grep -q "error_env_tlob" ../trace && { found=1; break; }
+	i=$((i+1))
+done
+
+echo "-:busy_work" >> "$TLOB_MONITOR" 2>/dev/null
+echo 0 > ../events/rv/error_env_tlob/enable
+echo 0 > monitors/tlob/enable
+
+[ "$found" = "1" ]
+
+# error_env_tlob must carry the clk_elapsed environment field.
+# The event label is "budget_exceeded" when detected by the hrtimer callback,
+# or the triggering sched event name when detected by the constraint path on a
+# preemption that races with the timer (common on PREEMPT_RT / VM).  Both are
+# valid detections; check the env field instead of the label.
+grep "error_env_tlob" ../trace | head -n 1 | grep -q "clk_elapsed="
+
+echo > ../trace
diff --git a/tools/testing/selftests/verification/tlob_target.c b/tools/testing/selftests/verification/tlob_target.c
new file mode 100644
index 000000000000..744215909c15
--- /dev/null
+++ b/tools/testing/selftests/verification/tlob_target.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tlob_target.c - uprobe target binary for tlob selftests.
+ *
+ * Provides two start/stop probe pairs:
+ *
+ *   tlob_busy_work  / tlob_busy_work_done  - busy-spin
+ *   tlob_sleep_work / tlob_sleep_work_done - nanosleep
+ *
+ * Usage: tlob_target <duration_ms> [mode] [iterations]
+ *
+ * mode is one of: busy (default), sleep, seq.
+ * Loops in 200 ms iterations until <duration_ms> has elapsed
+ * (0 = run until killed, capped at TLOB_MAX_DURATION_MS), or exactly
+ * [iterations] times when > 0.
+ * seq runs busy then sleep in each iteration (two ranges back to back).
+ */
+#define _GNU_SOURCE
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef noinline
+#define noinline __attribute__((noinline))
+#endif
+
+/*
+ * Ceiling for duration_ms == 0.  The live tests background the target and
+ * kill it from their EXIT trap; the cap bounds an orphan left behind when
+ * the trap does not run.
+ */
+#define TLOB_MAX_DURATION_MS	60000UL
+
+static inline int timespec_before(const struct timespec *a,
+				  const struct timespec *b)
+{
+	return a->tv_sec < b->tv_sec ||
+	       (a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec);
+}
+
+static void timespec_add_ms(struct timespec *ts, unsigned long ms)
+{
+	ts->tv_sec  += ms / 1000;
+	ts->tv_nsec += (long)(ms % 1000) * 1000000L;
+	if (ts->tv_nsec >= 1000000000L) {
+		ts->tv_sec++;
+		ts->tv_nsec -= 1000000000L;
+	}
+}
+
+/* stop probe; noinline keeps the entry point visible to uprobes */
+noinline void tlob_busy_work_done(void)
+{
+	asm volatile("" ::: "memory");
+}
+
+/* Busy-wait for at least duration_ms ms of wall-clock time. */
+static inline void __tlob_busy_wait(unsigned long duration_ms)
+{
+	struct timespec start, now;
+	unsigned long elapsed;
+
+	clock_gettime(CLOCK_MONOTONIC, &start);
+	do {
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		elapsed = (unsigned long)(now.tv_sec - start.tv_sec)
+			  * 1000000000UL
+			+ (unsigned long)(now.tv_nsec - start.tv_nsec);
+	} while (elapsed < duration_ms * 1000000UL);
+}
+
+/* start probe; busy-spin so the window is spent running */
+noinline void tlob_busy_work(unsigned long duration_ms)
+{
+	__tlob_busy_wait(duration_ms);
+	tlob_busy_work_done();
+}
+
+/* stop probe; noinline keeps the entry point visible to uprobes */
+noinline void tlob_sleep_work_done(void)
+{
+	asm volatile("" ::: "memory");
+}
+
+/* start probe; nanosleep so the window is spent sleeping */
+noinline void tlob_sleep_work(unsigned long duration_ms)
+{
+	struct timespec ts = {
+		.tv_sec  = duration_ms / 1000,
+		.tv_nsec = (long)(duration_ms % 1000) * 1000000L,
+	};
+	nanosleep(&ts, NULL);
+	tlob_sleep_work_done();
+}
+
+int main(int argc, char *argv[])
+{
+	unsigned long duration_ms = 0, iterations = 0, i = 0;
+	const char *mode = "busy";
+	struct timespec deadline, now;
+
+	if (argc >= 2)
+		duration_ms = strtoul(argv[1], NULL, 10);
+	if (argc >= 3)
+		mode = argv[2];
+	if (argc >= 4)
+		iterations = strtoul(argv[3], NULL, 10);
+
+	clock_gettime(CLOCK_MONOTONIC, &deadline);
+	timespec_add_ms(&deadline, duration_ms ? duration_ms : TLOB_MAX_DURATION_MS);
+
+	do {
+		if (strcmp(mode, "sleep") == 0)
+			tlob_sleep_work(200);
+		else if (strcmp(mode, "seq") == 0) {
+			tlob_busy_work(200);
+			tlob_sleep_work(200);
+		} else
+			tlob_busy_work(200);
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		i++;
+	} while (iterations ? i < iterations : timespec_before(&now, &deadline));
+
+	return 0;
+}
-- 
2.25.1


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

* [PATCH v7 14/14] selftests/ftrace: Walk up parent dirs to find test.d/functions
  2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
                   ` (12 preceding siblings ...)
  2026-10-11 19:39 ` [PATCH v7 13/14] selftests/verification: Add tlob selftests wen.yang
@ 2026-10-11 19:39 ` wen.yang
  13 siblings, 0 replies; 17+ messages in thread
From: wen.yang @ 2026-10-11 19:39 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

From: Wen Yang <wen.yang@linux.dev>

When a subdirectory is passed (e.g. an RV monitor's test directory),
ftracetest fell back to its own functions file, losing the RV-specific
check_requires handling for ':monitor' and ':reactor' requirements.

Walk up from OPT_TEST_DIR until a directory containing test.d/functions
is found, stopping after five levels so a stray argument cannot reach an
unrelated functions file further up the tree.  When no such directory is
found the previous default is kept.

Note that TOP_DIR also feeds LOG_TOP_DIR, so logs now follow the
directory the tests were found in: an RV run writes to
verification/logs rather than ftrace/logs.

Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
 tools/testing/selftests/ftrace/ftracetest | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest
index 0a56bf209f6c..3d3c3a4dfa34 100755
--- a/tools/testing/selftests/ftrace/ftracetest
+++ b/tools/testing/selftests/ftrace/ftracetest
@@ -159,9 +159,22 @@ parse_opts() { # opts
   if [ -n "$OPT_TEST_CASES" ]; then
     TEST_CASES=$OPT_TEST_CASES
   fi
-  if [ -n "$OPT_TEST_DIR" -a -f "$OPT_TEST_DIR"/test.d/functions ]; then
-    TOP_DIR=$OPT_TEST_DIR
-    TEST_DIR=$TOP_DIR/test.d
+  if [ -n "$OPT_TEST_DIR" ]; then
+    # A subdirectory of test.d can be given; walk up to the directory
+    # holding test.d/functions.  The bound keeps a stray directory from
+    # reaching an unrelated functions file further up the tree.
+    local dir=$OPT_TEST_DIR
+    local depth=0
+    while [ "$depth" -lt 5 ]; do
+      if [ -f "$dir/test.d/functions" ]; then
+        TOP_DIR=$dir
+        TEST_DIR=$TOP_DIR/test.d
+        break
+      fi
+      case "$dir" in /|//|.) break;; esac
+      dir=$(dirname "$dir")
+      depth=$((depth + 1))
+    done
   fi
 }
 
-- 
2.25.1


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

* Re: [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow
  2026-10-11 19:39 ` [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
@ 2026-10-12  7:47   ` kernel test robot
  2026-10-12  9:27   ` kernel test robot
  1 sibling, 0 replies; 17+ messages in thread
From: kernel test robot @ 2026-10-12  7:47 UTC (permalink / raw)
  To: wen.yang, Gabriele Monaco
  Cc: oe-kbuild-all, Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

Hi,

kernel test robot noticed the following build errors:

[auto build test ERROR on trace/for-next]
[also build test ERROR on shuah-kselftest/next shuah-kselftest/fixes linus/master v7.3-rc6 next-20261009]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/wen-yang-linux-dev/rv-Force-environment-reset-action-on-HA-monitor-start/20261012-033907
base:   https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link:    https://lore.kernel.org/r/cdaf33af866392428f1942cef4e442b1d106989c.1791745088.git.wen.yang%40linux.dev
patch subject: [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow
config: sh-randconfig-1001-20261012 (https://download.01.org/0day-ci/archive/20261012/202610121540.5HFVLTni-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 16.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261012/202610121540.5HFVLTni-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610121540.5HFVLTni-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   In file included from include/uapi/linux/posix_types.h:5,
                    from include/uapi/linux/types.h:14,
                    from include/linux/types.h:5,
                    from include/linux/kasan-checks.h:5,
                    from include/asm-generic/rwonce.h:26,
                    from ./arch/sh/include/generated/asm/rwonce.h:1,
                    from include/linux/compiler.h:369,
                    from include/linux/err.h:5,
                    from include/kunit/assert.h:12,
                    from include/kunit/test.h:12,
                    from include/rv/kunit.h:16,
                    from kernel/trace/rv/rv_monitors_test.c:10:
>> include/linux/stddef.h:16:33: error: 'struct ha_monitor' has no member named 'da_mon'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:66:16: note: in expansion of macro 'offsetof'
      66 | _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
         |                ^~~~~~~~
>> include/linux/stddef.h:16:33: error: expression in static assertion is not an integer
   include/rv/ha_monitor.h:66:16: note: in expansion of macro 'offsetof'
      66 | _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
         |                ^~~~~~~~
>> kernel/trace/rv/rv_monitors_test.c:21:23: warning: 'HA_TIMER_HRTIMER' is not defined, evaluates to '0' [-Wundef]
      21 | #define HA_TIMER_TYPE HA_TIMER_HRTIMER
         |                       ^~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:122:5: note: in expansion of macro 'HA_TIMER_TYPE'
     122 | #if HA_TIMER_TYPE == HA_TIMER_WHEEL
         |     ^~~~~~~~~~~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
>> include/rv/ha_monitor.h:122:22: warning: 'HA_TIMER_WHEEL' is not defined, evaluates to '0' [-Wundef]
     122 | #if HA_TIMER_TYPE == HA_TIMER_WHEEL
         |                      ^~~~~~~~~~~~~~
   In file included from include/linux/init.h:5,
                    from include/linux/printk.h:6,
                    from include/kunit/assert.h:13:
   include/rv/ha_monitor.h: In function 'ha_monitor_init_env':
>> include/linux/container_of.h:8:47: error: 'struct ha_monitor' has no member named 'da_mon'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                               ^~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/linux/container_of.h:20:43: note: in expansion of macro 'typeof_member'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                                           ^~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:192:37: note: in expansion of macro 'to_ha_monitor'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   include/linux/compiler_types.h:610:27: error: expression in static assertion is not an integer
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:192:37: note: in expansion of macro 'to_ha_monitor'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
>> include/linux/stddef.h:16:33: error: 'struct ha_monitor' has no member named 'da_mon'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:23:34: note: in expansion of macro 'offsetof'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:192:37: note: in expansion of macro 'to_ha_monitor'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   include/rv/ha_monitor.h: In function 'ha_monitor_reset_env':
>> include/linux/container_of.h:8:47: error: 'struct ha_monitor' has no member named 'da_mon'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                               ^~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/linux/container_of.h:20:43: note: in expansion of macro 'typeof_member'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                                           ^~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:208:37: note: in expansion of macro 'to_ha_monitor'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   include/linux/compiler_types.h:610:27: error: expression in static assertion is not an integer
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:208:37: note: in expansion of macro 'to_ha_monitor'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
>> include/linux/stddef.h:16:33: error: 'struct ha_monitor' has no member named 'da_mon'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:23:34: note: in expansion of macro 'offsetof'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:208:37: note: in expansion of macro 'to_ha_monitor'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   In file included from <command-line>:
   include/rv/ha_monitor.h: In function 'ha_monitor_env_invalid':
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/linux/compiler_types.h:635:53: note: in definition of macro '__unqual_scalar_typeof'
     635 | #define __unqual_scalar_typeof(x) __typeof_unqual__(x)
         |                                                     ^
   include/asm-generic/rwonce.h:50:9: note: in expansion of macro '__READ_ONCE'
      50 |         __READ_ONCE(x);                                                 \
         |         ^~~~~~~~~~~
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:219:32: error: 'struct ha_monitor' has no member named 'env_store'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                                ^~
   include/asm-generic/rwonce.h:44:73: note: in definition of macro '__READ_ONCE'
      44 | #define __READ_ONCE(x)  (*(const volatile __unqual_scalar_typeof(x) *)&(x))
         |                                                                         ^
   include/rv/ha_monitor.h:219:16: note: in expansion of macro 'READ_ONCE'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                ^~~~~~~~~
>> include/rv/ha_monitor.h:217:80: warning: parameter 'env' set but not used [-Wunused-but-set-parameter=]
     217 | static inline bool ha_monitor_env_invalid(struct ha_monitor *ha_mon, enum envs env)
         |                                                                      ~~~~~~~~~~^~~
   include/rv/ha_monitor.h: In function 'ha_monitor_handle_constraint':
>> include/linux/container_of.h:8:47: error: 'struct ha_monitor' has no member named 'da_mon'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                               ^~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/linux/container_of.h:20:43: note: in expansion of macro 'typeof_member'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                                           ^~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:286:37: note: in expansion of macro 'to_ha_monitor'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   include/linux/compiler_types.h:610:27: error: expression in static assertion is not an integer
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: in definition of macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   include/linux/container_of.h:20:9: note: in expansion of macro 'static_assert'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ^~~~~~~~~~~~~
   include/linux/container_of.h:20:23: note: in expansion of macro '__same_type'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |                       ^~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:286:37: note: in expansion of macro 'to_ha_monitor'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
>> include/linux/stddef.h:16:33: error: 'struct ha_monitor' has no member named 'da_mon'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:23:34: note: in expansion of macro 'offsetof'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^~~~~~~~
   include/rv/ha_monitor.h:68:27: note: in expansion of macro 'container_of'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^~~~~~~~~~~~
   include/rv/ha_monitor.h:286:37: note: in expansion of macro 'to_ha_monitor'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~
   include/rv/ha_monitor.h: In function '__ha_monitor_timer_callback':
>> include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:635:53: note: in definition of macro '__unqual_scalar_typeof'
     635 | #define __unqual_scalar_typeof(x) __typeof_unqual__(x)
         |                                                     ^
   include/rv/ha_monitor.h:321:22: note: in expansion of macro 'smp_load_acquire'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                      ^~~~~~~~~~~~~~~~
>> include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/barrier.h:204:44: note: in expansion of macro 'READ_ONCE'
     204 |         __unqual_scalar_typeof(*p) ___p1 = READ_ONCE(*p);               \
         |                                            ^~~~~~~~~
   include/rv/ha_monitor.h:321:22: note: in expansion of macro 'smp_load_acquire'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                      ^~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/barrier.h:204:44: note: in expansion of macro 'READ_ONCE'
     204 |         __unqual_scalar_typeof(*p) ___p1 = READ_ONCE(*p);               \
         |                                            ^~~~~~~~~
   include/rv/ha_monitor.h:321:22: note: in expansion of macro 'smp_load_acquire'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                      ^~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/barrier.h:204:44: note: in expansion of macro 'READ_ONCE'
     204 |         __unqual_scalar_typeof(*p) ___p1 = READ_ONCE(*p);               \
         |                                            ^~~~~~~~~
   include/rv/ha_monitor.h:321:22: note: in expansion of macro 'smp_load_acquire'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                      ^~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
     702 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ^~~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:9: note: in expansion of macro 'compiletime_assert'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |         ^~~~~~~~~~~~~~~~~~
   include/asm-generic/rwonce.h:36:28: note: in expansion of macro '__native_word'
      36 |         compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),  \
         |                            ^~~~~~~~~~~~~
   include/asm-generic/rwonce.h:49:9: note: in expansion of macro 'compiletime_assert_rwonce_type'
      49 |         compiletime_assert_rwonce_type(x);                              \
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/barrier.h:204:44: note: in expansion of macro 'READ_ONCE'
     204 |         __unqual_scalar_typeof(*p) ___p1 = READ_ONCE(*p);               \
         |                                            ^~~~~~~~~
   include/rv/ha_monitor.h:321:22: note: in expansion of macro 'smp_load_acquire'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                      ^~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:321:46: error: 'struct ha_monitor' has no member named 'da_mon'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                              ^~
   include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
     682 |                 if (!(condition))                                       \


vim +16 include/linux/stddef.h

6e2182874324727 Richard Knutsson 2006-09-30  14  
^1da177e4c3f415 Linus Torvalds   2005-04-16  15  #undef offsetof
14e83077d55ff4b Rasmus Villemoes 2022-03-23 @16  #define offsetof(TYPE, MEMBER)	__builtin_offsetof(TYPE, MEMBER)
3876488444e7123 Denys Vlasenko   2015-03-09  17  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow
  2026-10-11 19:39 ` [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
  2026-10-12  7:47   ` kernel test robot
@ 2026-10-12  9:27   ` kernel test robot
  1 sibling, 0 replies; 17+ messages in thread
From: kernel test robot @ 2026-10-12  9:27 UTC (permalink / raw)
  To: wen.yang, Gabriele Monaco
  Cc: llvm, oe-kbuild-all, Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang

Hi,

kernel test robot noticed the following build errors:

[auto build test ERROR on trace/for-next]
[also build test ERROR on shuah-kselftest/next shuah-kselftest/fixes linus/master v7.3-rc6 next-20261009]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/wen-yang-linux-dev/rv-Force-environment-reset-action-on-HA-monitor-start/20261012-033907
base:   https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link:    https://lore.kernel.org/r/cdaf33af866392428f1942cef4e442b1d106989c.1791745088.git.wen.yang%40linux.dev
patch subject: [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow
config: x86_64-randconfig-1005-20261012 (https://download.01.org/0day-ci/archive/20261012/202610121745.TCxIxDec-lkp@intel.com/config)
compiler: clang version 22.1.8 (https://github.com/llvm/llvm-project ca7933e47d3a3451d81e72ac174dcb5aa28b59d1)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261012/202610121745.TCxIxDec-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610121745.TCxIxDec-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   In file included from kernel/trace/rv/rv_monitors_test.c:71:
>> include/rv/ha_monitor.h:66:16: error: no member named 'da_mon' in 'ha_monitor'
      66 | _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
         |                ^                           ~~~~~~
   include/linux/stddef.h:16:32: note: expanded from macro 'offsetof'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^                        ~~~~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
>> include/rv/ha_monitor.h:122:5: warning: 'HA_TIMER_HRTIMER' is not defined, evaluates to 0 [-Wundef]
     122 | #if HA_TIMER_TYPE == HA_TIMER_WHEEL
         |     ^
   kernel/trace/rv/rv_monitors_test.c:21:23: note: expanded from macro 'HA_TIMER_TYPE'
      21 | #define HA_TIMER_TYPE HA_TIMER_HRTIMER
         |                       ^
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:122:22: warning: 'HA_TIMER_WHEEL' is not defined, evaluates to 0 [-Wundef]
     122 | #if HA_TIMER_TYPE == HA_TIMER_WHEEL
         |                      ^
>> include/rv/ha_monitor.h:192:30: error: no member named 'da_mon' in 'struct ha_monitor'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:63: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
   include/linux/container_of.h:20:56: note: expanded from macro 'container_of'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
      21 |                       __same_type(*(ptr), void),                        \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      22 |                       "pointer type mismatch in container_of()");       \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:8:45: note: expanded from macro 'typeof_member'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                                 ^
   include/linux/compiler_types.h:610:74: note: expanded from macro '__same_type'
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                                                                          ^
   include/linux/build_bug.h:79:50: note: expanded from macro 'static_assert'
      79 | #define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
         |                                  ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: expanded from macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:192:30: error: no member named 'da_mon' in 'ha_monitor'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^                                   ~~~~~~
   include/linux/container_of.h:23:27: note: expanded from macro 'container_of'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^              ~~~~~~
   include/linux/stddef.h:16:32: note: expanded from macro 'offsetof'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^                        ~~~~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
>> include/rv/ha_monitor.h:192:21: error: initializing 'struct ha_monitor *' with an expression of incompatible type 'void'
     192 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                            ^        ~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:208:30: error: no member named 'da_mon' in 'struct ha_monitor'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:63: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
   include/linux/container_of.h:20:56: note: expanded from macro 'container_of'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
      21 |                       __same_type(*(ptr), void),                        \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      22 |                       "pointer type mismatch in container_of()");       \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:8:45: note: expanded from macro 'typeof_member'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                                 ^
   include/linux/compiler_types.h:610:74: note: expanded from macro '__same_type'
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                                                                          ^
   include/linux/build_bug.h:79:50: note: expanded from macro 'static_assert'
      79 | #define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
         |                                  ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: expanded from macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:208:30: error: no member named 'da_mon' in 'ha_monitor'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^                                   ~~~~~~
   include/linux/container_of.h:23:27: note: expanded from macro 'container_of'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^              ~~~~~~
   include/linux/stddef.h:16:32: note: expanded from macro 'offsetof'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^                        ~~~~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:208:21: error: initializing 'struct ha_monitor *' with an expression of incompatible type 'void'
     208 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                            ^        ~~~~~~~~~~~~~~~~~~~~~
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
>> include/rv/ha_monitor.h:219:27: error: no member named 'env_store' in 'struct ha_monitor'
     219 |         return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
         |                          ~~~~~~  ^
   include/rv/ha_monitor.h:286:30: error: no member named 'da_mon' in 'struct ha_monitor'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:63: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
   include/linux/container_of.h:20:56: note: expanded from macro 'container_of'
      20 |         static_assert(__same_type(*(ptr), typeof_member(type, member)) || \
         |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
      21 |                       __same_type(*(ptr), void),                        \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      22 |                       "pointer type mismatch in container_of()");       \
         |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/container_of.h:8:45: note: expanded from macro 'typeof_member'
       8 | #define typeof_member(T, m)     typeof(((T*)0)->m)
         |                                                 ^
   include/linux/compiler_types.h:610:74: note: expanded from macro '__same_type'
     610 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
         |                                                                          ^
   include/linux/build_bug.h:79:50: note: expanded from macro 'static_assert'
      79 | #define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
         |                                  ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:80:56: note: expanded from macro '__static_assert'
      80 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
         |                                                        ^~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:286:30: error: no member named 'da_mon' in 'ha_monitor'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                                     ^~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:68:27: note: expanded from macro 'to_ha_monitor'
      68 | #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
         |                           ^                                   ~~~~~~
   include/linux/container_of.h:23:27: note: expanded from macro 'container_of'
      23 |         (type *)((void *)(ptr) - offsetof(type, member)); })
         |                                  ^              ~~~~~~
   include/linux/stddef.h:16:32: note: expanded from macro 'offsetof'
      16 | #define offsetof(TYPE, MEMBER)  __builtin_offsetof(TYPE, MEMBER)
         |                                 ^                        ~~~~~~
   In file included from kernel/trace/rv/rv_monitors_test.c:71:
   include/rv/ha_monitor.h:286:21: error: initializing 'struct ha_monitor *' with an expression of incompatible type 'void'
     286 |         struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
         |                            ^        ~~~~~~~~~~~~~~~~~~~~~
   include/rv/ha_monitor.h:321:41: error: no member named 'da_mon' in 'struct ha_monitor'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                        ~~~~~~  ^
   include/rv/ha_monitor.h:321:41: error: no member named 'da_mon' in 'struct ha_monitor'
     321 |         curr_state = smp_load_acquire(&ha_mon->da_mon.curr_state);
         |                                        ~~~~~~  ^
   fatal error: too many errors emitted, stopping now [-ferror-limit=]
   2 warnings and 20 errors generated.


vim +66 include/rv/ha_monitor.h

f5587d1b6ec938 Gabriele Monaco 2026-03-30   64  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   65  /* This simplifies things since da_mon and ha_mon coexist in the same union */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  @66  _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
f5587d1b6ec938 Gabriele Monaco 2026-03-30   67  	       "da_mon must be the first element in an ha_mon!");
f5587d1b6ec938 Gabriele Monaco 2026-03-30   68  #define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
f5587d1b6ec938 Gabriele Monaco 2026-03-30   69  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   70  #define ENV_MAX CONCATENATE(env_max_, MONITOR_NAME)
f5587d1b6ec938 Gabriele Monaco 2026-03-30   71  #define ENV_MAX_STORED CONCATENATE(env_max_stored_, MONITOR_NAME)
f5587d1b6ec938 Gabriele Monaco 2026-03-30   72  #define envs CONCATENATE(envs_, MONITOR_NAME)
f5587d1b6ec938 Gabriele Monaco 2026-03-30   73  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   74  /* Environment storage before being reset */
f5587d1b6ec938 Gabriele Monaco 2026-03-30   75  #define ENV_INVALID_VALUE U64_MAX
f5587d1b6ec938 Gabriele Monaco 2026-03-30   76  /* Error with no event occurs only on timeouts */
f5587d1b6ec938 Gabriele Monaco 2026-03-30   77  #define EVENT_NONE EVENT_MAX
f5587d1b6ec938 Gabriele Monaco 2026-03-30   78  #define EVENT_NONE_LBL "none"
f5587d1b6ec938 Gabriele Monaco 2026-03-30   79  #define ENV_BUFFER_SIZE 64
f5587d1b6ec938 Gabriele Monaco 2026-03-30   80  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   81  #ifdef CONFIG_RV_REACTORS
f5587d1b6ec938 Gabriele Monaco 2026-03-30   82  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   83  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30   84   * ha_react - trigger the reaction after a failed environment constraint
f5587d1b6ec938 Gabriele Monaco 2026-03-30   85   *
f5587d1b6ec938 Gabriele Monaco 2026-03-30   86   * The transition from curr_state with event is otherwise valid, but the
f5587d1b6ec938 Gabriele Monaco 2026-03-30   87   * environment constraint is false. This function can be called also with no
f5587d1b6ec938 Gabriele Monaco 2026-03-30   88   * event from a timer (state constraints only).
f5587d1b6ec938 Gabriele Monaco 2026-03-30   89   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30   90  static void ha_react(enum states curr_state, enum events event, char *env)
f5587d1b6ec938 Gabriele Monaco 2026-03-30   91  {
f5587d1b6ec938 Gabriele Monaco 2026-03-30   92  	rv_react(&rv_this,
f5587d1b6ec938 Gabriele Monaco 2026-03-30   93  		 "rv: monitor %s does not allow event %s on state %s with env %s\n",
f5587d1b6ec938 Gabriele Monaco 2026-03-30   94  		 __stringify(MONITOR_NAME),
f5587d1b6ec938 Gabriele Monaco 2026-03-30   95  		 event == EVENT_NONE ? EVENT_NONE_LBL : model_get_event_name(event),
f5587d1b6ec938 Gabriele Monaco 2026-03-30   96  		 model_get_state_name(curr_state), env);
f5587d1b6ec938 Gabriele Monaco 2026-03-30   97  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30   98  
f5587d1b6ec938 Gabriele Monaco 2026-03-30   99  #else /* CONFIG_RV_REACTOR */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  100  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  101  static void ha_react(enum states curr_state, enum events event, char *env) { }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  102  #endif
f5587d1b6ec938 Gabriele Monaco 2026-03-30  103  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  104  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  105   * model_get_state_name - return the (string) name of the given state
f5587d1b6ec938 Gabriele Monaco 2026-03-30  106   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  107  static char *model_get_env_name(enum envs env)
f5587d1b6ec938 Gabriele Monaco 2026-03-30  108  {
f5587d1b6ec938 Gabriele Monaco 2026-03-30  109  	if ((env < 0) || (env >= ENV_MAX))
f5587d1b6ec938 Gabriele Monaco 2026-03-30  110  		return "INVALID";
f5587d1b6ec938 Gabriele Monaco 2026-03-30  111  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  112  	return RV_AUTOMATON_NAME.env_names[env];
f5587d1b6ec938 Gabriele Monaco 2026-03-30  113  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  114  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  115  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  116   * Monitors requiring a timer implementation need to request it explicitly.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  117   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  118  #ifndef HA_TIMER_TYPE
f5587d1b6ec938 Gabriele Monaco 2026-03-30  119  #define HA_TIMER_TYPE HA_TIMER_NONE
f5587d1b6ec938 Gabriele Monaco 2026-03-30  120  #endif
f5587d1b6ec938 Gabriele Monaco 2026-03-30  121  
f5587d1b6ec938 Gabriele Monaco 2026-03-30 @122  #if HA_TIMER_TYPE == HA_TIMER_WHEEL
f5587d1b6ec938 Gabriele Monaco 2026-03-30  123  static void ha_monitor_timer_callback(struct timer_list *timer);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  124  #elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
f5587d1b6ec938 Gabriele Monaco 2026-03-30  125  static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  126  #endif
f5587d1b6ec938 Gabriele Monaco 2026-03-30  127  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  128  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  129   * ktime_get_ns is expensive, since we usually don't require precise accounting
f5587d1b6ec938 Gabriele Monaco 2026-03-30  130   * of changes within the same event, cache the current time at the beginning of
f5587d1b6ec938 Gabriele Monaco 2026-03-30  131   * the constraint handler and use the cache for subsequent calls.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  132   * Monitors without ns clocks automatically skip this.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  133   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  134  #ifdef HA_CLK_NS
f5587d1b6ec938 Gabriele Monaco 2026-03-30  135  #define ha_get_ns() ktime_get_ns()
f5587d1b6ec938 Gabriele Monaco 2026-03-30  136  #else
f5587d1b6ec938 Gabriele Monaco 2026-03-30  137  #define ha_get_ns() 0
f5587d1b6ec938 Gabriele Monaco 2026-03-30  138  #endif /* HA_CLK_NS */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  139  
74e17bd6fc8ed7 Gabriele Monaco 2026-06-01  140  static bool ha_mon_destroying;
74e17bd6fc8ed7 Gabriele Monaco 2026-06-01  141  
700782ec8f6589 Gabriele Monaco 2026-06-01  142  static int ha_monitor_init(void)
700782ec8f6589 Gabriele Monaco 2026-06-01  143  {
700782ec8f6589 Gabriele Monaco 2026-06-01  144  	int ret;
700782ec8f6589 Gabriele Monaco 2026-06-01  145  
74e17bd6fc8ed7 Gabriele Monaco 2026-06-01  146  	WRITE_ONCE(ha_mon_destroying, false);
700782ec8f6589 Gabriele Monaco 2026-06-01  147  	ret = da_monitor_init();
700782ec8f6589 Gabriele Monaco 2026-06-01  148  	if (ret == 0)
700782ec8f6589 Gabriele Monaco 2026-06-01  149  		ha_monitor_enable_hook();
700782ec8f6589 Gabriele Monaco 2026-06-01  150  	return ret;
700782ec8f6589 Gabriele Monaco 2026-06-01  151  }
700782ec8f6589 Gabriele Monaco 2026-06-01  152  
700782ec8f6589 Gabriele Monaco 2026-06-01  153  static void ha_monitor_destroy(void)
700782ec8f6589 Gabriele Monaco 2026-06-01  154  {
74e17bd6fc8ed7 Gabriele Monaco 2026-06-01  155  	WRITE_ONCE(ha_mon_destroying, true);
700782ec8f6589 Gabriele Monaco 2026-06-01  156  	ha_monitor_disable_hook();
700782ec8f6589 Gabriele Monaco 2026-06-01  157  	da_monitor_destroy();
700782ec8f6589 Gabriele Monaco 2026-06-01  158  }
700782ec8f6589 Gabriele Monaco 2026-06-01  159  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  160  /* Should be supplied by the monitor */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  161  static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
a8db17cc2158f8 Gabriele Monaco 2026-10-12  162  static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  163  static bool ha_verify_constraint(struct ha_monitor *ha_mon,
f5587d1b6ec938 Gabriele Monaco 2026-03-30  164  				 enum states curr_state,
f5587d1b6ec938 Gabriele Monaco 2026-03-30  165  				 enum events event,
f5587d1b6ec938 Gabriele Monaco 2026-03-30  166  				 enum states next_state,
f5587d1b6ec938 Gabriele Monaco 2026-03-30  167  				 u64 time_ns);
a8db17cc2158f8 Gabriele Monaco 2026-10-12  168  #ifdef HA_NO_RESET
a8db17cc2158f8 Gabriele Monaco 2026-10-12  169  static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) { }
a8db17cc2158f8 Gabriele Monaco 2026-10-12  170  #endif
f5587d1b6ec938 Gabriele Monaco 2026-03-30  171  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  172  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  173   * ha_monitor_reset_all_stored - reset all environment variables in the monitor
f5587d1b6ec938 Gabriele Monaco 2026-03-30  174   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  175  static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon)
f5587d1b6ec938 Gabriele Monaco 2026-03-30  176  {
a8db17cc2158f8 Gabriele Monaco 2026-10-12  177  	u64 time_ns = ha_get_ns();
a8db17cc2158f8 Gabriele Monaco 2026-10-12  178  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  179  	for (int i = 0; i < ENV_MAX_STORED; i++)
a8db17cc2158f8 Gabriele Monaco 2026-10-12  180  		ha_reset_env(ha_mon, i, time_ns);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  181  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  182  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  183  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  184   * ha_monitor_init_env - setup timer and reset all environment
f5587d1b6ec938 Gabriele Monaco 2026-03-30  185   *
f5587d1b6ec938 Gabriele Monaco 2026-03-30  186   * Called from a hook in the DA start functions, it supplies the da_mon
f5587d1b6ec938 Gabriele Monaco 2026-03-30  187   * corresponding to the current ha_mon.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  188   * Not all hybrid automata require the timer, still set it for simplicity.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  189   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  190  static inline void ha_monitor_init_env(struct da_monitor *da_mon)
f5587d1b6ec938 Gabriele Monaco 2026-03-30  191  {
f5587d1b6ec938 Gabriele Monaco 2026-03-30 @192  	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  193  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  194  	ha_monitor_reset_all_stored(ha_mon);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  195  	ha_setup_timer(ha_mon);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  196  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  197  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  198  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  199   * ha_monitor_reset_env - stop timer and reset all environment
f5587d1b6ec938 Gabriele Monaco 2026-03-30  200   *
f5587d1b6ec938 Gabriele Monaco 2026-03-30  201   * Called from a hook in the DA reset functions, it supplies the da_mon
f5587d1b6ec938 Gabriele Monaco 2026-03-30  202   * corresponding to the current ha_mon.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  203   * Not all hybrid automata require the timer, still clear it for simplicity.
713c9e1ea76238 Gabriele Monaco 2026-06-01  204   * Monitors that never started have their timer uninitialized, do not stop those.
f5587d1b6ec938 Gabriele Monaco 2026-03-30  205   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  206  static inline void ha_monitor_reset_env(struct da_monitor *da_mon)
f5587d1b6ec938 Gabriele Monaco 2026-03-30  207  {
f5587d1b6ec938 Gabriele Monaco 2026-03-30  208  	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  209  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  210  	if (likely(da_monitoring(da_mon)))
f5587d1b6ec938 Gabriele Monaco 2026-03-30  211  		ha_cancel_timer(ha_mon);
f5587d1b6ec938 Gabriele Monaco 2026-03-30  212  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  213  
f5587d1b6ec938 Gabriele Monaco 2026-03-30  214  /*
f5587d1b6ec938 Gabriele Monaco 2026-03-30  215   * ha_monitor_env_invalid - return true if env has not been initialised
f5587d1b6ec938 Gabriele Monaco 2026-03-30  216   */
f5587d1b6ec938 Gabriele Monaco 2026-03-30  217  static inline bool ha_monitor_env_invalid(struct ha_monitor *ha_mon, enum envs env)
f5587d1b6ec938 Gabriele Monaco 2026-03-30  218  {
f5587d1b6ec938 Gabriele Monaco 2026-03-30 @219  	return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
f5587d1b6ec938 Gabriele Monaco 2026-03-30  220  }
f5587d1b6ec938 Gabriele Monaco 2026-03-30  221  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

end of thread, other threads:[~2026-10-12  9:28 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-11 19:39 [PATCH v7 00/14] rv: Add task latency over budget RV monitor wen.yang
2026-10-11 19:39 ` [PATCH v7 01/14] rv: Force environment reset action on HA monitor start wen.yang
2026-10-11 19:39 ` [PATCH v7 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
2026-10-11 19:39 ` [PATCH v7 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
2026-10-12  7:47   ` kernel test robot
2026-10-12  9:27   ` kernel test robot
2026-10-11 19:39 ` [PATCH v7 04/14] rv: Disarm HA timers on the storage free path wen.yang
2026-10-11 19:39 ` [PATCH v7 05/14] rv: Add a KUnit test for " wen.yang
2026-10-11 19:39 ` [PATCH v7 06/14] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable wen.yang
2026-10-11 19:39 ` [PATCH v7 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
2026-10-11 19:39 ` [PATCH v7 08/14] rv: Add per-object teardown hook da_extra_cleanup wen.yang
2026-10-11 19:39 ` [PATCH v7 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
2026-10-11 19:39 ` [PATCH v7 10/14] rv: Add tlob model DOT file wen.yang
2026-10-11 19:39 ` [PATCH v7 11/14] rv: Add tlob hybrid automaton monitor wen.yang
2026-10-11 19:39 ` [PATCH v7 12/14] rv: Add KUnit tests for the tlob monitor wen.yang
2026-10-11 19:39 ` [PATCH v7 13/14] selftests/verification: Add tlob selftests wen.yang
2026-10-11 19:39 ` [PATCH v7 14/14] selftests/ftrace: Walk up parent dirs to find test.d/functions wen.yang

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

all inboxes | Powered by JetHome®