summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/trace/rv/monitor_rtapp.rst67
-rw-r--r--include/rv/da_monitor.h31
-rw-r--r--include/rv/ha_monitor.h85
-rw-r--r--include/rv/kunit.h73
-rw-r--r--include/rv/ltl_monitor.h14
-rw-r--r--kernel/trace/rv/Kconfig15
-rw-r--r--kernel/trace/rv/Makefile2
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss.c36
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss_kunit.c38
-rw-r--r--kernel/trace/rv/monitors/nomiss/nomiss_kunit.h35
-rw-r--r--kernel/trace/rv/monitors/opid/opid.c12
-rw-r--r--kernel/trace/rv/monitors/opid/opid_kunit.c33
-rw-r--r--kernel/trace/rv/monitors/opid/opid_kunit.h23
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault.c20
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault_kunit.c34
-rw-r--r--kernel/trace/rv/monitors/pagefault/pagefault_kunit.h24
-rw-r--r--kernel/trace/rv/monitors/rtapp/Kconfig2
-rw-r--r--kernel/trace/rv/monitors/sco/sco.c13
-rw-r--r--kernel/trace/rv/monitors/sco/sco_kunit.c29
-rw-r--r--kernel/trace/rv/monitors/sco/sco_kunit.h24
-rw-r--r--kernel/trace/rv/monitors/sleep/Kconfig1
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep.c107
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep.h142
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep_kunit.c59
-rw-r--r--kernel/trace/rv/monitors/sleep/sleep_kunit.h29
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw.c14
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw_kunit.c33
-rw-r--r--kernel/trace/rv/monitors/sssw/sssw_kunit.h30
-rw-r--r--kernel/trace/rv/monitors/stall/stall.c2
-rw-r--r--kernel/trace/rv/monitors/sts/sts.c19
-rw-r--r--kernel/trace/rv/monitors/sts/sts_kunit.c39
-rw-r--r--kernel/trace/rv/monitors/sts/sts_kunit.h33
-rw-r--r--kernel/trace/rv/monitors/wakeup/Kconfig16
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup.c153
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup.h92
-rw-r--r--kernel/trace/rv/monitors/wakeup/wakeup_trace.h14
-rw-r--r--kernel/trace/rv/rv.c86
-rw-r--r--kernel/trace/rv/rv_monitors_test.c182
-rw-r--r--kernel/trace/rv/rv_reactors.c1
-rw-r--r--kernel/trace/rv/rv_trace.h1
-rw-r--r--tools/testing/selftests/verification/test.d/rv_deadline.tc23
-rw-r--r--tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc10
-rw-r--r--tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc4
-rw-r--r--tools/testing/selftests/verification/test.d/rv_stall.tc33
-rw-r--r--tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc32
-rw-r--r--tools/verification/models/rtapp/sleep.ltl11
-rw-r--r--tools/verification/models/rtapp/wakeup.ltl5
-rw-r--r--tools/verification/rv/Makefile5
-rw-r--r--tools/verification/rv/src/rv.c24
-rw-r--r--tools/verification/rv/tests/rv_list.t48
-rw-r--r--tools/verification/rv/tests/rv_mon.t95
-rw-r--r--tools/verification/rvgen/Makefile5
-rw-r--r--tools/verification/rvgen/__main__.py22
-rw-r--r--tools/verification/rvgen/rvgen/automata.py643
-rw-r--r--tools/verification/rvgen/rvgen/dot2c.py10
-rw-r--r--tools/verification/rvgen/rvgen/dot2k.py311
-rw-r--r--tools/verification/rvgen/rvgen/generator.py51
-rw-r--r--tools/verification/rvgen/rvgen/kunit.py194
-rw-r--r--tools/verification/rvgen/rvgen/ltl2ba.py202
-rw-r--r--tools/verification/rvgen/rvgen/ltl2k.py2
-rw-r--r--tools/verification/rvgen/rvgen/templates/container/main.c2
-rw-r--r--tools/verification/rvgen/rvgen/templates/dot2k/main.c2
-rw-r--r--tools/verification/rvgen/rvgen/templates/kunit.c33
-rw-r--r--tools/verification/rvgen/rvgen/templates/ltl2k/main.c8
-rw-r--r--tools/verification/rvgen/tests/golden/da_global/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/da_global/da_global.c95
-rw-r--r--tools/verification/rvgen/tests/golden/da_global/da_global.h47
-rw-r--r--tools/verification/rvgen/tests/golden/da_global/da_global_trace.h15
-rw-r--r--tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig11
-rw-r--r--tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c119
-rw-r--r--tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h64
-rw-r--r--tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h15
-rw-r--r--tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c105
-rw-r--r--tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h64
-rw-r--r--tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h15
-rw-r--r--tools/verification/rvgen/tests/golden/ha_percpu/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c227
-rw-r--r--tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h72
-rw-r--r--tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h19
-rw-r--r--tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c107
-rw-r--r--tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h108
-rw-r--r--tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h14
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c107
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h108
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c33
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak1
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h22
-rw-r--r--tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h14
-rw-r--r--tools/verification/rvgen/tests/golden/test_container/Kconfig5
-rw-r--r--tools/verification/rvgen/tests/golden/test_container/test_container.c35
-rw-r--r--tools/verification/rvgen/tests/golden/test_container/test_container.h3
-rw-r--r--tools/verification/rvgen/tests/golden/test_da/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_da/test_da.c95
-rw-r--r--tools/verification/rvgen/tests/golden/test_da/test_da.h47
-rw-r--r--tools/verification/rvgen/tests/golden/test_da/test_da_trace.h15
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c107
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h47
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c33
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h23
-rw-r--r--tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h15
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha/test_ha.c230
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha/test_ha.h72
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h19
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c243
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h88
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c33
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h24
-rw-r--r--tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h19
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl/Kconfig11
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c108
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h108
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h14
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig9
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c107
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h108
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c33
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h22
-rw-r--r--tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h14
-rw-r--r--tools/verification/rvgen/tests/rvgen_container.t20
-rw-r--r--tools/verification/rvgen/tests/rvgen_kunit.t41
-rw-r--r--tools/verification/rvgen/tests/rvgen_monitor.t87
-rw-r--r--tools/verification/rvgen/tests/specs/test_da.dot16
-rw-r--r--tools/verification/rvgen/tests/specs/test_da2.dot19
-rw-r--r--tools/verification/rvgen/tests/specs/test_ha.dot27
-rw-r--r--tools/verification/rvgen/tests/specs/test_invalid.dot8
-rw-r--r--tools/verification/rvgen/tests/specs/test_invalid.ltl1
-rw-r--r--tools/verification/rvgen/tests/specs/test_invalid_ha.dot16
-rw-r--r--tools/verification/rvgen/tests/specs/test_ltl.ltl1
-rw-r--r--tools/verification/tests/engine.sh175
135 files changed, 6106 insertions, 893 deletions
diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst
index c8104eda924a..b95994ade14a 100644
--- a/Documentation/trace/rv/monitor_rtapp.rst
+++ b/Documentation/trace/rv/monitor_rtapp.rst
@@ -51,12 +51,13 @@ The `sleep` monitor reports real-time threads sleeping in a manner that may
cause undesirable latency. Real-time applications should only put a real-time
thread to sleep for one of the following reasons:
- - Cyclic work: real-time thread sleeps waiting for the next cycle. For this
- case, only the `clock_nanosleep` syscall should be used with `TIMER_ABSTIME`
- (to avoid time drift) and `CLOCK_MONOTONIC` (to avoid the clock being
- changed). No other method is safe for real-time. For example, threads
- waiting for timerfd can be woken by softirq which provides no real-time
- guarantee.
+ - Cyclic work: real-time thread sleeps waiting for the next
+ cycle. For this case, only the `clock_nanosleep` syscall should be
+ used with `TIMER_ABSTIME` (to avoid time drift). Additionally,
+ `CLOCK_REALTIME` should not be used (to avoid the clock being
+ changed). No other method is safe for real-time. For example,
+ threads waiting for timerfd can be woken by softirq which provides
+ no real-time guarantee.
- Real-time thread waiting for something to happen (e.g. another thread
releasing shared resources, or a completion signal from another thread). In
this case, only futexes (FUTEX_LOCK_PI, FUTEX_LOCK_PI2 or one of
@@ -66,6 +67,8 @@ thread to sleep for one of the following reasons:
variables as safe for real-time. As an alternative, the librtpi library
exists to provide a conditional variable implementation that is correct for
real-time applications in Linux.
+ - Real-time thread waiting for events using `epoll_wait`, which is a
+ real-time-safe syscall for sleeping as it uses PI-aware locking.
Beside the reason for sleeping, the eventual waker should also be
real-time-safe. Namely, one of:
@@ -92,38 +95,34 @@ assessment.
The monitor's specification is::
- RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST))
+ RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST))
- RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD)
- and ((not WAKE) until RT_FRIENDLY_WAKE)
+ RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON
+ and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE)
RT_VALID_SLEEP_REASON = FUTEX_WAIT
or RT_FRIENDLY_NANOSLEEP
+ or EPOLL_WAIT
RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP
and NANOSLEEP_TIMER_ABSTIME
- and NANOSLEEP_CLOCK_MONOTONIC
+ and not NANOSLEEP_CLOCK_REALTIME
RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO
or WOKEN_BY_HARDIRQ
or WOKEN_BY_NMI
- or KTHREAD_SHOULD_STOP
+ or ABORT_SLEEP
ALLOWLIST = BLOCK_ON_RT_MUTEX
or FUTEX_LOCK_PI
- or TASK_IS_RCU
- or TASK_IS_MIGRATION
-
-Beside the scenarios described above, this specification also handle some
-special cases:
-
- - `KERNEL_THREAD`: kernel tasks do not have any pattern that can be recognized
- as valid real-time sleeping reasons. Therefore sleeping reason is not
- checked for kernel tasks.
- - `KTHREAD_SHOULD_STOP`: a non-real-time thread may stop a real-time kernel
- thread by waking it and waiting for it to exit (`kthread_stop()`). This
- wakeup is safe for real-time.
- - `ALLOWLIST`: to handle known false positives with the kernel.
+
+`ABORT_SLEEP` represents a task restoring its state to `TASK_RUNNING` before
+entering the scheduler. In this case, the task does not actually block, so the
+task is back to runnable without any wakeup sequence unsafe for real-time.
+
+Beside the scenarios described above, this specification also defines an allow list
+to handle some special cases:
+
- `BLOCK_ON_RT_MUTEX` is included in the allowlist due to its implementation.
In the release path of rt_mutex, a boosted task is de-boosted before waking
the rt_mutex's waiter. Consequently, the monitor may see a real-time-unsafe
@@ -131,3 +130,23 @@ special cases:
real-time-safe because preemption is disabled for the duration.
- `FUTEX_LOCK_PI` is included in the allowlist for the same reason as
`BLOCK_ON_RT_MUTEX`.
+
+Monitor wakeup
+++++++++++++++
+
+The `wakeup` monitor reports real-time threads being woken by lower-priority threads,
+which is a hint of priority inversion. Its specification is::
+
+ RULE = always (((RT and USER_THREAD) imply
+ (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST))
+
+ ALLOWLIST = BLOCK_ON_RT_MUTEX
+ or FUTEX_LOCK_PI
+
+The `sleep` monitor already reports this type of problem. The difference is the
+context in which the problem is reported. While the `sleep` monitor reports the problem
+in the context of the wakee, this `wakeup` monitor reports the problem in the context of
+the waker. This monitor complement the `sleep` monitor, giving user better
+understanding of the issue. For instance, to debug a lower-priority task waking a
+higher-priority task scenario, user can enable both `wakeup` monitor and `sleep`
+monitor to get the stack traces of both tasks.
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 34b8fba9ecd4..e3cf85c9ce55 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -16,6 +16,7 @@
#include <rv/automata.h>
#include <linux/rv.h>
+#include <rv/kunit.h>
#include <linux/stringify.h>
#include <linux/bug.h>
#include <linux/sched.h>
@@ -311,6 +312,11 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
return &tsk->rv[task_mon_slot].da_mon;
}
+static inline void da_reset(struct task_struct *tsk)
+{
+ da_monitor_reset(da_get_monitor(tsk));
+}
+
/*
* da_get_target - return the task associated to the monitor
*/
@@ -334,12 +340,12 @@ static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
struct task_struct *g, *p;
int cpu;
- read_lock(&tasklist_lock);
- for_each_process_thread(g, p)
- reset(da_get_monitor(p));
+ scoped_guard(read_lock, &tasklist_lock) {
+ for_each_process_thread(g, p)
+ reset(da_get_monitor(p));
+ }
for_each_present_cpu(cpu)
reset(da_get_monitor(idle_task(cpu)));
- read_unlock(&tasklist_lock);
}
static void da_monitor_reset_all(void)
@@ -908,4 +914,21 @@ static inline void da_reset(da_id_type id, monitor_target target)
}
#endif /* RV_MON_TYPE */
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#if RV_MON_TYPE == RV_MON_PER_TASK
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = &task_mon_slot, \
+ .task_reset = da_reset, \
+}
+#else
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .monitor_init = da_monitor_init, \
+ .monitor_destroy = da_monitor_destroy, \
+}
+#endif /* RV_MON_TYPE */
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+
#endif
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 28d3c74cabfc..6e1c7fe5449a 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -327,19 +327,8 @@ static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon)
}
/*
- * The clock variables have 2 different representations in the env_store:
- * - The guard representation is the timestamp of the last reset
- * - The invariant representation is the timestamp when the invariant expires
- * As the representations are incompatible, care must be taken when switching
- * between them: the invariant representation can only be used when starting a
- * timer when the previous representation was guard (e.g. no other invariant
- * started since the last reset operation).
- * Likewise, switching from invariant to guard representation without a reset
- * can be done only by subtracting the exact value used to start the invariant.
- *
- * Reading the environment variable (ha_get_clk) also reflects this difference
- * any reads in states that have an invariant return the (possibly negative)
- * time since expiration, other reads return the time since last reset.
+ * The clock variables store the time epoch - the timestamp when the clock was last reset.
+ * They are read by subtracting the time epoch from the current time.
*/
/*
@@ -353,31 +342,21 @@ static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64
{
WRITE_ONCE(ha_mon->env_store[env], time_ns);
}
-static inline void ha_set_invariant_ns(struct ha_monitor *ha_mon, enum envs env,
- u64 value, u64 time_ns)
-{
- WRITE_ONCE(ha_mon->env_store[env], time_ns + value);
-}
-static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon,
- enum envs env, u64 time_ns)
+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;
+ return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_ns;
}
/*
* ha_invariant_passed_ns - prepare the invariant and return the time since reset
*/
-static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env,
- u64 expire, u64 time_ns)
+static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
{
- u64 passed = 0;
-
if (env < 0 || env >= ENV_MAX_STORED)
return 0;
if (ha_monitor_env_invalid(ha_mon, env))
return 0;
- passed = ha_get_env(ha_mon, env, time_ns);
- ha_set_invariant_ns(ha_mon, env, expire - passed, time_ns);
- return passed;
+ return ha_get_env(ha_mon, env, time_ns);
}
/*
@@ -391,32 +370,21 @@ static inline void ha_reset_clk_jiffy(struct ha_monitor *ha_mon, enum envs env)
{
WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64());
}
-static inline void ha_set_invariant_jiffy(struct ha_monitor *ha_mon,
- enum envs env, u64 value)
+static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, enum envs env,
+ u64 time_ns, u64 expire_jiffy)
{
- WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64() + value);
-}
-static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon,
- enum envs env, u64 time_ns)
-{
- return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64());
-
+ return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64() - expire_jiffy);
}
/*
* ha_invariant_passed_jiffy - prepare the invariant and return the time since reset
*/
-static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env,
- u64 expire, u64 time_ns)
+static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
{
- u64 passed = 0;
-
if (env < 0 || env >= ENV_MAX_STORED)
return 0;
if (ha_monitor_env_invalid(ha_mon, env))
return 0;
- passed = ha_get_env(ha_mon, env, time_ns);
- ha_set_invariant_jiffy(ha_mon, env, expire - passed);
- return passed;
+ return ha_get_env(ha_mon, env, time_ns);
}
/*
@@ -463,14 +431,14 @@ static inline void ha_setup_timer(struct ha_monitor *ha_mon)
static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
u64 expire, u64 time_ns)
{
- u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns);
+ u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);
mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
}
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, expire, time_ns);
+ u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns);
ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns);
@@ -516,7 +484,7 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
u64 expire, u64 time_ns)
{
int mode = HRTIMER_MODE_REL_HARD;
- u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns);
+ u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns);
if (RV_MON_TYPE == RV_MON_PER_CPU)
mode |= HRTIMER_MODE_PINNED;
@@ -525,7 +493,7 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
u64 expire, u64 time_ns)
{
- u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns);
+ 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);
@@ -558,4 +526,25 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon) { }
#endif
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#ifdef RV_MON_OPS_INIT
+#undef RV_MON_OPS_INIT
+#endif
+
+#if RV_MON_TYPE == RV_MON_PER_TASK
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = &task_mon_slot, \
+ .task_reset = da_reset, \
+}
+#else
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .monitor_init = ha_monitor_init, \
+ .monitor_destroy = ha_monitor_destroy, \
+}
+#endif /* RV_MON_TYPE */
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+
#endif
diff --git a/include/rv/kunit.h b/include/rv/kunit.h
new file mode 100644
index 000000000000..31e0b93c40ea
--- /dev/null
+++ b/include/rv/kunit.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * Declaration of wrappers to allow mocking core functionality, like current,
+ * and other testing utilities.
+ * Necessary only when mocking may be needed. If the RV KUnit test is
+ * enabled, the wrappers incur an additional function call overhead.
+ */
+
+#ifndef _RV_KUNIT_H
+#define _RV_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <kunit/test.h>
+#include <kunit/test-bug.h>
+#include <linux/delay.h>
+
+int rv_set_testing(struct kunit_suite *suite);
+void rv_clear_testing(struct kunit_suite *suite);
+
+#define RV_KUNIT_MAX_MOCK_TASKS 8
+
+struct rv_kunit_ctx {
+ int reactions, expected;
+ int mock_task_count;
+ struct task_struct *mock_tasks[RV_KUNIT_MAX_MOCK_TASKS];
+};
+
+#define RV_KUNIT_EXPECT_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ++ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#define RV_KUNIT_EXPECT_NO_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#define RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) \
+ for (int __done = ({ RV_KUNIT_EXPECT_NO_REACTION(test, ctx); 0; }); \
+ !__done; \
+ __done = ({ RV_KUNIT_EXPECT_REACTION(test, ctx); 1; }))
+
+struct rv_kunit_mon {
+ struct rv_monitor *rv_this;
+ int (*monitor_init)(void);
+ void (*monitor_destroy)(void);
+ bool is_per_task;
+ int *task_slot;
+ void (*task_reset)(struct task_struct *task);
+};
+
+void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon);
+void teardown_test(void *arg);
+struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test);
+
+void rv_mock_current(struct task_struct *tsk);
+struct task_struct *rv_get_mock_current(void);
+
+#define rv_get_current() (unlikely(kunit_get_current_test()) ? rv_get_mock_current() : current)
+
+#else /* !CONFIG_RV_MONITORS_KUNIT_TEST */
+
+#define rv_get_current() current
+
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+#endif /* _RV_KUNIT_H */
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index 38e792401f76..e9fd8265a3da 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -9,6 +9,7 @@
#include <linux/stringify.h>
#include <linux/seq_buf.h>
#include <rv/instrumentation.h>
+#include <rv/kunit.h>
#include <trace/events/task.h>
#include <trace/events/sched.h>
@@ -16,8 +17,7 @@
#error "Please include $(MODEL_NAME).h generated by rvgen"
#endif
-#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
-static struct rv_monitor RV_MONITOR_NAME;
+static struct rv_monitor rv_this;
static int ltl_monitor_slot = RV_PER_TASK_MONITOR_INIT;
@@ -85,7 +85,7 @@ static void ltl_monitor_destroy(void)
static void ltl_illegal_state(struct task_struct *task, struct ltl_monitor *mon)
{
CONCATENATE(trace_error_, MONITOR_NAME)(task);
- rv_react(&RV_MONITOR_NAME, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n",
+ rv_react(&rv_this, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n",
task->comm, task->pid);
}
@@ -172,3 +172,11 @@ static void __maybe_unused ltl_atom_pulse(struct task_struct *task, enum ltl_ato
ltl_atom_set(mon, atom, !value);
ltl_validate(task, mon);
}
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = &ltl_monitor_slot, \
+}
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 3884b14df375..efa930f94ea4 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -76,6 +76,7 @@ source "kernel/trace/rv/monitors/opid/Kconfig"
source "kernel/trace/rv/monitors/rtapp/Kconfig"
source "kernel/trace/rv/monitors/pagefault/Kconfig"
source "kernel/trace/rv/monitors/sleep/Kconfig"
+source "kernel/trace/rv/monitors/wakeup/Kconfig"
# Add new rtapp monitors here
source "kernel/trace/rv/monitors/stall/Kconfig"
@@ -111,3 +112,17 @@ config RV_REACT_PANIC
help
Enables the panic reactor. The panic reactor emits a printk()
message if an exception is found and panic()s the system.
+
+config RV_MONITORS_KUNIT_TEST
+ tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS
+ depends on KUNIT && RV && RV_REACTORS
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the RV (Runtime Verification) monitors.
+ These tests verify that monitors correctly detect violations by
+ triggering fake events and validating the expected reactions.
+
+ Enabling this may slightly increase overhead of some monitors if any
+ unrelated KUnit test is running.
+
+ If unsure, say N.
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 94498da35b37..cdbf68c84f5a 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -20,7 +20,9 @@ obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
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
# Add new monitors here
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
+obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 8ead8783c29f..6e47d379f777 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -57,24 +57,12 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states next_state, u64 time_ns)
{
if (curr_state == ready_nomiss)
- return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+ return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon));
else if (curr_state == running_nomiss)
- return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+ return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon));
return true;
}
-static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
- enum states curr_state, enum events event,
- enum states next_state, u64 time_ns)
-{
- if (curr_state == next_state)
- return;
- if (curr_state == ready_nomiss)
- ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
- else if (curr_state == running_nomiss)
- ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
-}
-
static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
@@ -122,8 +110,6 @@ static bool ha_verify_constraint(struct ha_monitor *ha_mon,
if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
return false;
- ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
-
if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
return false;
@@ -291,3 +277,21 @@ module_exit(unregister_nomiss);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "nomiss_kunit.h"
+
+const struct rv_nomiss_ops rv_nomiss_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .deadline_thresh = &deadline_thresh,
+ .handle_dl_replenish = handle_dl_replenish,
+ .handle_dl_throttle = handle_dl_throttle,
+ .handle_dl_server_stop = handle_dl_server_stop,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_newtask = handle_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_nomiss_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
new file mode 100644
index 000000000000..763129e2a990
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "nomiss_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_NOMISS)
+
+static void rv_test_nomiss(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_nomiss_ops.mon);
+
+ target->pid = 99;
+ target->policy = SCHED_DEADLINE;
+ target->dl.runtime = 10000;
+ target->dl.dl_deadline = 20000;
+
+ rv_nomiss_ops.handle_newtask(NULL, target, 0);
+
+ /* Task gets preempted and can't terminate before deadline */
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ rv_nomiss_ops.handle_dl_replenish(NULL, &target->dl, 0, DL_TASK);
+ udelay(10);
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ udelay(15 + div_u64(*rv_nomiss_ops.deadline_thresh, 1000));
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ }
+}
+
+#else
+#define rv_test_nomiss rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
new file mode 100644
index 000000000000..2be779c5dbaa
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __NOMISS_KUNIT_H
+#define __NOMISS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_nomiss_ops {
+ struct rv_kunit_mon mon;
+ const u64 *deadline_thresh;
+ void (*handle_dl_replenish)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_throttle)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_server_stop)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *tsk);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_nomiss_ops;
+#endif
+
+#endif /* __NOMISS_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 3b6a85e815b8..9ae619f176fa 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -115,3 +115,15 @@ module_exit(unregister_opid);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("opid: operations with preemption and irq disabled.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "opid_kunit.h"
+
+const struct rv_opid_ops rv_opid_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_need_resched = handle_sched_need_resched,
+ .handle_sched_waking = handle_sched_waking,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_opid_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.c b/kernel/trace/rv/monitors/opid/opid_kunit.c
new file mode 100644
index 000000000000..3cb087a74241
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "opid_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_OPID)
+
+static void rv_test_opid(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_opid_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+ KUNIT_EXPECT_TRUE(test, preemptible());
+
+ /* Wakeup with preemption and interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_opid_ops.handle_sched_waking(NULL, NULL);
+
+ /* Need resched with interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ scoped_guard(preempt)
+ rv_opid_ops.handle_sched_need_resched(NULL, NULL, 0, TIF_NEED_RESCHED);
+ }
+}
+
+#else
+#define rv_test_opid rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.h b/kernel/trace/rv/monitors/opid/opid_kunit.h
new file mode 100644
index 000000000000..4969c6175957
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __OPID_KUNIT_H
+#define __OPID_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_opid_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_need_resched)(void *data, struct task_struct *tsk, int cpu, int tif);
+ void (*handle_sched_waking)(void *data, struct task_struct *p);
+} rv_opid_ops;
+#endif
+
+#endif /* __OPID_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 9fe6123b2200..c599fc19fc88 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -38,7 +38,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs,
unsigned long error_code)
{
- ltl_atom_pulse(current, LTL_PAGEFAULT, true);
+ ltl_atom_pulse(rv_get_current(), LTL_PAGEFAULT, true);
}
static int enable_pagefault(void)
@@ -63,7 +63,7 @@ static void disable_pagefault(void)
ltl_monitor_destroy();
}
-static struct rv_monitor rv_pagefault = {
+static struct rv_monitor rv_this = {
.name = "pagefault",
.description = "Monitor that RT tasks do not raise page faults",
.enable = enable_pagefault,
@@ -72,12 +72,12 @@ static struct rv_monitor rv_pagefault = {
static int __init register_pagefault(void)
{
- return rv_register_monitor(&rv_pagefault, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_pagefault(void)
{
- rv_unregister_monitor(&rv_pagefault);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_pagefault);
@@ -86,3 +86,15 @@ module_exit(unregister_pagefault);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "pagefault_kunit.h"
+
+const struct rv_pagefault_ops rv_pagefault_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_page_fault = handle_page_fault,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_pagefault_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
new file mode 100644
index 000000000000..06369960b008
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <linux/sched/deadline.h>
+#include <linux/sched/rt.h>
+#include "pagefault_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_PAGEFAULT)
+
+static void rv_test_pagefault(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_pagefault_ops.mon);
+
+ /* Initial pagefault when non-RT to start the model without failure */
+ target->policy = SCHED_NORMAL;
+ target->prio = MAX_RT_PRIO + 20;
+ rv_pagefault_ops.handle_task_newtask(NULL, target, 0);
+ rv_mock_current(target);
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+
+ /* RT task has a page fault */
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 1;
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+}
+
+#else
+#define rv_test_pagefault rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
new file mode 100644
index 000000000000..2f9652f08b3f
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __PAGEFAULT_KUNIT_H
+#define __PAGEFAULT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_pagefault_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_page_fault)(void *data, unsigned long address, struct pt_regs *regs,
+ unsigned long error_code);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_pagefault_ops;
+#endif
+
+#endif /* __PAGEFAULT_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/rtapp/Kconfig b/kernel/trace/rv/monitors/rtapp/Kconfig
index 1ce9370a9ba8..1fcd7a400ded 100644
--- a/kernel/trace/rv/monitors/rtapp/Kconfig
+++ b/kernel/trace/rv/monitors/rtapp/Kconfig
@@ -1,6 +1,6 @@
config RV_MON_RTAPP
depends on RV
- depends on RV_PER_TASK_MONITORS >= 2
+ depends on RV_PER_TASK_MONITORS >= 3
bool "rtapp monitor"
help
Collection of monitors to check for common problems with real-time
diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c
index 5a3bd5e16e62..1ef1b96e859d 100644
--- a/kernel/trace/rv/monitors/sco/sco.c
+++ b/kernel/trace/rv/monitors/sco/sco.c
@@ -83,3 +83,16 @@ module_exit(unregister_sco);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sco: scheduling context operations.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sco_kunit.h"
+
+const struct rv_sco_ops rv_sco_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sco_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.c b/kernel/trace/rv/monitors/sco/sco_kunit.c
new file mode 100644
index 000000000000..5e59bcbfcf0b
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sco_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SCO)
+
+static void rv_test_sco(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sco_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+
+ /* Set state while scheduling */
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sco_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+}
+
+#else
+#define rv_test_sco rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.h b/kernel/trace/rv/monitors/sco/sco_kunit.h
new file mode 100644
index 000000000000..567757df6b1d
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SCO_KUNIT_H
+#define __SCO_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sco_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sco_ops;
+#endif
+
+#endif /* __SCO_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sleep/Kconfig b/kernel/trace/rv/monitors/sleep/Kconfig
index 6b7a122e7b47..d6ec3e9a91b6 100644
--- a/kernel/trace/rv/monitors/sleep/Kconfig
+++ b/kernel/trace/rv/monitors/sleep/Kconfig
@@ -5,7 +5,6 @@ config RV_MON_SLEEP
select RV_LTL_MONITOR
depends on HAVE_SYSCALL_TRACEPOINTS
depends on RV_MON_RTAPP
- select TRACE_IRQFLAGS
default y
select LTL_MON_EVENTS_ID
bool "sleep monitor"
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 8dfe5ec13e19..b82537251e09 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -36,16 +36,14 @@ static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
ltl_atom_set(mon, LTL_SLEEP, false);
- ltl_atom_set(mon, LTL_WAKE, false);
+ ltl_atom_set(mon, LTL_SCHEDULE_IN, false);
ltl_atom_set(mon, LTL_ABORT_SLEEP, false);
ltl_atom_set(mon, LTL_WOKEN_BY_HARDIRQ, false);
ltl_atom_set(mon, LTL_WOKEN_BY_NMI, false);
ltl_atom_set(mon, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, false);
if (task_creation) {
- ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
@@ -54,34 +52,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false);
}
- if (task->flags & PF_KTHREAD) {
- ltl_atom_set(mon, LTL_KERNEL_THREAD, true);
-
- /* kernel tasks do not do syscall */
- ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
- ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
- ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
- ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
-
- if (strstarts(task->comm, "migration/"))
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, true);
- else
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
-
- if (strstarts(task->comm, "rcu"))
- ltl_atom_set(mon, LTL_TASK_IS_RCU, true);
- else
- ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
- } else {
- ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
- ltl_atom_set(mon, LTL_KERNEL_THREAD, false);
- ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
- ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
- }
-
+ ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD));
}
static void handle_sched_set_state(void *data, struct task_struct *task, int state)
@@ -92,17 +63,17 @@ static void handle_sched_set_state(void *data, struct task_struct *task, int sta
ltl_atom_pulse(task, LTL_ABORT_SLEEP, true);
}
-static void handle_sched_wakeup(void *data, struct task_struct *task)
+static void handle_sched_exit(void *data, bool is_switch)
{
- ltl_atom_pulse(task, LTL_WAKE, true);
+ ltl_atom_pulse(rv_get_current(), LTL_SCHEDULE_IN, true);
}
static void handle_sched_waking(void *data, struct task_struct *task)
{
- if (this_cpu_read(hardirq_context)) {
+ if (in_hardirq()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true);
} else if (in_task()) {
- if (current->prio <= task->prio)
+ if (rv_get_current()->prio <= task->prio)
ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true);
} else if (in_nmi()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true);
@@ -112,12 +83,12 @@ static void handle_sched_waking(void *data, struct task_struct *task)
static void handle_contention_begin(void *data, void *lock, unsigned int flags)
{
if (flags & LCB_F_RT)
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, true);
}
static void handle_contention_end(void *data, void *lock, int ret)
{
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, false);
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
@@ -126,7 +97,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
unsigned long args[6];
int op, cmd;
- mon = ltl_get_monitor(current);
+ mon = ltl_get_monitor(rv_get_current());
switch (id) {
#ifdef __NR_clock_nanosleep
@@ -135,11 +106,10 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_clock_nanosleep_time64
case __NR_clock_nanosleep_time64:
#endif
- syscall_get_arguments(current, regs, args);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI);
+ syscall_get_arguments(rv_get_current(), regs, args);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, args[0] == CLOCK_REALTIME);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, true);
break;
#ifdef __NR_futex
@@ -148,25 +118,25 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_futex_time64
case __NR_futex_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
op = args[1];
cmd = op & FUTEX_CMD_MASK;
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_LOCK_PI2:
- ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_LOCK_PI, true);
break;
case FUTEX_WAIT:
case FUTEX_WAIT_BITSET:
case FUTEX_WAIT_REQUEUE_PI:
- ltl_atom_update(current, LTL_FUTEX_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_WAIT, true);
break;
}
break;
#ifdef __NR_epoll_wait
case __NR_epoll_wait:
- ltl_atom_update(current, LTL_EPOLL_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_EPOLL_WAIT, true);
break;
#endif
}
@@ -174,21 +144,14 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
{
- struct ltl_monitor *mon = ltl_get_monitor(current);
+ struct ltl_monitor *mon = ltl_get_monitor(rv_get_current());
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
- ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
+ ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false);
-}
-
-static void handle_kthread_stop(void *data, struct task_struct *task)
-{
- /* FIXME: this could race with other tracepoint handlers */
- ltl_atom_update(task, LTL_KTHREAD_SHOULD_STOP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, false);
}
static int enable_sleep(void)
@@ -200,11 +163,10 @@ static int enable_sleep(void)
return retval;
rv_attach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
- rv_attach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
+ rv_attach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit);
rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
- rv_attach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_attach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_attach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
return 0;
@@ -213,18 +175,17 @@ static int enable_sleep(void)
static void disable_sleep(void)
{
rv_detach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
- rv_detach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
+ rv_detach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit);
rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
- rv_detach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_detach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_detach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
ltl_monitor_destroy();
}
-static struct rv_monitor rv_sleep = {
+static struct rv_monitor rv_this = {
.name = "sleep",
.description = "Monitor that RT tasks do not undesirably sleep",
.enable = enable_sleep,
@@ -233,12 +194,12 @@ static struct rv_monitor rv_sleep = {
static int __init register_sleep(void)
{
- return rv_register_monitor(&rv_sleep, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_sleep(void)
{
- rv_unregister_monitor(&rv_sleep);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_sleep);
@@ -247,3 +208,21 @@ module_exit(unregister_sleep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sleep_kunit.h"
+
+const struct rv_sleep_ops rv_sleep_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_waking = handle_sched_waking,
+ .handle_sched_exit = handle_sched_exit,
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_contention_begin = handle_contention_begin,
+ .handle_contention_end = handle_contention_end,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_sys_exit = handle_sys_exit,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sleep_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h
index 95dc2727c059..44e593f41e6a 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.h
+++ b/kernel/trace/rv/monitors/sleep/sleep.h
@@ -18,16 +18,12 @@ enum ltl_atom {
LTL_EPOLL_WAIT,
LTL_FUTEX_LOCK_PI,
LTL_FUTEX_WAIT,
- LTL_KERNEL_THREAD,
- LTL_KTHREAD_SHOULD_STOP,
- LTL_NANOSLEEP_CLOCK_MONOTONIC,
- LTL_NANOSLEEP_CLOCK_TAI,
+ LTL_NANOSLEEP_CLOCK_REALTIME,
LTL_NANOSLEEP_TIMER_ABSTIME,
LTL_RT,
+ LTL_SCHEDULE_IN,
LTL_SLEEP,
- LTL_TASK_IS_MIGRATION,
- LTL_TASK_IS_RCU,
- LTL_WAKE,
+ LTL_USER_THREAD,
LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
LTL_WOKEN_BY_HARDIRQ,
LTL_WOKEN_BY_NMI,
@@ -44,16 +40,12 @@ static const char *ltl_atom_str(enum ltl_atom atom)
"ep_wa",
"fu_lo_pi",
"fu_wa",
- "ker_th",
- "kth_sh_st",
- "na_cl_mo",
- "na_cl_ta",
+ "na_cl_re",
"na_ti_ab",
"rt",
- "sl",
- "ta_mi",
- "ta_rc",
- "wak",
+ "sch_in",
+ "sle",
+ "us_th",
"wo_eq_hi_pr",
"wo_ha",
"wo_nm",
@@ -81,47 +73,41 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
- bool wake = test_bit(LTL_WAKE, mon->atoms);
- bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
- bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
+ bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
- bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
- bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
- bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
- bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
+ bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
- bool val42 = task_is_rcu || task_is_migration;
- bool val43 = futex_lock_pi || val42;
- bool val5 = block_on_rt_mutex || val43;
- bool val34 = abort_sleep || kthread_should_stop;
- bool val35 = woken_by_nmi || val34;
- bool val36 = woken_by_hardirq || val35;
- bool val14 = woken_by_equal_or_higher_prio || val36;
- bool val13 = !wake;
- bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
- bool val27 = nanosleep_timer_abstime && val26;
- bool val18 = clock_nanosleep && val27;
+ bool val7 = block_on_rt_mutex || futex_lock_pi;
+ bool val32 = woken_by_nmi || abort_sleep;
+ bool val33 = woken_by_hardirq || val32;
+ bool val14 = woken_by_equal_or_higher_prio || val33;
+ bool val13 = !schedule_in;
+ bool val25 = !nanosleep_clock_realtime;
+ bool val26 = nanosleep_timer_abstime && val25;
+ bool val18 = clock_nanosleep && val26;
bool val20 = val18 || epoll_wait;
- bool val9 = futex_wait || val20;
- bool val11 = val9 || kernel_thread;
+ bool val11 = futex_wait || val20;
+ bool val3 = !user_thread;
bool val2 = !sleep;
+ bool val4 = val2 || val3;
bool val1 = !rt;
- bool val3 = val1 || val2;
+ bool val5 = val1 || val4;
- if (val3)
+ if (val5)
__set_bit(S0, mon->states);
if (val11 && val13)
__set_bit(S1, mon->states);
if (val11 && val14)
__set_bit(S4, mon->states);
- if (val5)
+ if (val7)
__set_bit(S5, mon->states);
}
@@ -132,131 +118,125 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
- bool wake = test_bit(LTL_WAKE, mon->atoms);
- bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
- bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
+ bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
- bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
- bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
- bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
- bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
+ bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
- bool val42 = task_is_rcu || task_is_migration;
- bool val43 = futex_lock_pi || val42;
- bool val5 = block_on_rt_mutex || val43;
- bool val34 = abort_sleep || kthread_should_stop;
- bool val35 = woken_by_nmi || val34;
- bool val36 = woken_by_hardirq || val35;
- bool val14 = woken_by_equal_or_higher_prio || val36;
- bool val13 = !wake;
- bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
- bool val27 = nanosleep_timer_abstime && val26;
- bool val18 = clock_nanosleep && val27;
+ bool val7 = block_on_rt_mutex || futex_lock_pi;
+ bool val32 = woken_by_nmi || abort_sleep;
+ bool val33 = woken_by_hardirq || val32;
+ bool val14 = woken_by_equal_or_higher_prio || val33;
+ bool val13 = !schedule_in;
+ bool val25 = !nanosleep_clock_realtime;
+ bool val26 = nanosleep_timer_abstime && val25;
+ bool val18 = clock_nanosleep && val26;
bool val20 = val18 || epoll_wait;
- bool val9 = futex_wait || val20;
- bool val11 = val9 || kernel_thread;
+ bool val11 = futex_wait || val20;
+ bool val3 = !user_thread;
bool val2 = !sleep;
+ bool val4 = val2 || val3;
bool val1 = !rt;
- bool val3 = val1 || val2;
+ bool val5 = val1 || val4;
switch (state) {
case S0:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S1:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S2:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S3:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S4:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S5:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
case S6:
if (val11 && val13)
__set_bit(S1, next);
- if (val13 && val3)
+ if (val13 && val5)
__set_bit(S2, next);
- if (val14 && val3)
+ if (val14 && val5)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val13 && val5)
+ if (val13 && val7)
__set_bit(S6, next);
- if (val14 && val5)
+ if (val14 && val7)
__set_bit(S7, next);
break;
case S7:
- if (val3)
+ if (val5)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
- if (val5)
+ if (val7)
__set_bit(S5, next);
break;
}
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.c b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
new file mode 100644
index 000000000000..17df5baf1ec2
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <uapi/linux/futex.h>
+#include "sleep_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SLEEP)
+
+static void rv_test_sleep(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+ unsigned long args[6] = {0};
+ struct pt_regs regs = {0};
+
+ prepare_test(test, &rv_sleep_ops.mon);
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 2;
+ other->policy = SCHED_FIFO;
+ other->prio = MAX_RT_PRIO - 1;
+ rv_sleep_ops.handle_task_newtask(NULL, target, 0);
+
+ /* RT task sleeps on a non RT-friendly nanosleep */
+ rv_mock_current(target);
+ args[0] = CLOCK_REALTIME;
+ syscall_set_arguments(target, &regs, args);
+#ifdef __NR_clock_nanosleep
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_clock_nanosleep);
+#elif defined(__NR_clock_nanosleep_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_clock_nanosleep_time64);
+#endif
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sleep_ops.handle_sys_exit(NULL, NULL, 0);
+
+ /* RT task woken up by lower priority task */
+ args[1] = FUTEX_WAIT;
+ syscall_set_arguments(target, &regs, args);
+ rv_mock_current(target);
+#ifdef __NR_futex
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_futex);
+#elif defined(__NR_futex_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, &regs, __NR_futex_time64);
+#endif
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_mock_current(other);
+ rv_sleep_ops.handle_sched_waking(NULL, target);
+ rv_mock_current(target);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_exit(NULL, true);
+}
+
+#else
+#define rv_test_sleep rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.h b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
new file mode 100644
index 000000000000..3ebf8d2699f2
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SLEEP_KUNIT_H
+#define __SLEEP_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sleep_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_waking)(void *data, struct task_struct *task);
+ void (*handle_sched_exit)(void *data, bool is_switch);
+ void (*handle_sched_set_state)(void *data, struct task_struct *task, int state);
+ void (*handle_contention_begin)(void *data, void *lock, unsigned int flags);
+ void (*handle_contention_end)(void *data, void *lock, int ret);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_sys_exit)(void *data, struct pt_regs *regs, long ret);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_sleep_ops;
+#endif
+
+#endif /* __SLEEP_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c
index a91321c890cd..fbfde32dc136 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.c
+++ b/kernel/trace/rv/monitors/sssw/sssw.c
@@ -112,3 +112,17 @@ module_exit(unregister_sssw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sssw: set state sleep and wakeup.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sssw_kunit.h"
+
+const struct rv_sssw_ops rv_sssw_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_signal_deliver = handle_signal_deliver,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sssw_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.c b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
new file mode 100644
index 000000000000..a95faf859c60
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sssw_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SSSW)
+
+static void rv_test_sssw(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sssw_ops.mon);
+
+ /* Suspend without setting to sleepable */
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+
+ /* Switch in after suspension without wakeup */
+ rv_sssw_ops.handle_sched_wakeup(NULL, target);
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+}
+
+#else
+#define rv_test_sssw rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.h b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
new file mode 100644
index 000000000000..6513daa7afba
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SSSW_KUNIT_H
+#define __SSSW_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sssw_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *p);
+ void (*handle_signal_deliver)(void *data, int sig,
+ struct kernel_siginfo *info,
+ struct k_sigaction *ka);
+} rv_sssw_ops;
+#endif
+
+#endif /* __SSSW_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c
index 3c38fb1a0159..b265578f845c 100644
--- a/kernel/trace/rv/monitors/stall/stall.c
+++ b/kernel/trace/rv/monitors/stall/stall.c
@@ -38,7 +38,7 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states next_state, u64 time_ns)
{
if (curr_state == enqueued_stall)
- return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns);
+ return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns, threshold_jiffies);
return true;
}
diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c
index ce031cbf202a..2a044cf925b1 100644
--- a/kernel/trace/rv/monitors/sts/sts.c
+++ b/kernel/trace/rv/monitors/sts/sts.c
@@ -152,3 +152,22 @@ module_exit(unregister_sts);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sts: schedule implies task switch.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sts_kunit.h"
+
+const struct rv_sts_ops rv_sts_ops = {
+ .mon = RV_MON_OPS_INIT(),
+#ifdef CONFIG_X86_LOCAL_APIC
+ .handle_vector_irq_entry = handle_vector_irq_entry,
+#endif
+ .handle_irq_disable = handle_irq_disable,
+ .handle_irq_enable = handle_irq_enable,
+ .handle_irq_entry = handle_irq_entry,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sts_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.c b/kernel/trace/rv/monitors/sts/sts_kunit.c
new file mode 100644
index 000000000000..a07316fff091
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sts_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_STS)
+
+static void rv_test_sts(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sts_ops.mon);
+ /* Per-CPU monitor, make sure we don't change CPU mid-test */
+ guard(migrate)();
+
+ /* Switch without disabling interrupts */
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+
+ /* Schedule from interrupt context */
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ rv_sts_ops.handle_irq_disable(NULL, 0, 0);
+ rv_sts_ops.handle_irq_entry(NULL, 0, NULL);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ rv_sts_ops.handle_irq_enable(NULL, 0, 0);
+}
+
+#else
+#define rv_test_sts rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.h b/kernel/trace/rv/monitors/sts/sts_kunit.h
new file mode 100644
index 000000000000..dede4e098c1f
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __STS_KUNIT_H
+#define __STS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sts_ops {
+ struct rv_kunit_mon mon;
+#ifdef CONFIG_X86_LOCAL_APIC
+ void (*handle_vector_irq_entry)(void *data, int vector);
+#endif
+ void (*handle_irq_disable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_enable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_entry)(void *data, int irq, struct irqaction *action);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sts_ops;
+#endif
+
+#endif /* __STS_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/wakeup/Kconfig b/kernel/trace/rv/monitors/wakeup/Kconfig
new file mode 100644
index 000000000000..98f618f0e01d
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_WAKEUP
+ depends on RV
+ depends on RV_MON_RTAPP
+ depends on HAVE_SYSCALL_TRACEPOINTS
+ default y
+ select LTL_MON_EVENTS_ID
+ bool "wakeup monitor"
+ help
+ This monitor detects a lower-priority task waking up a
+ higher-priority task. The RV_MON_SLEEP monitor already
+ detects this case, but this monitor detects in the context
+ of the waker task instead. This and RV_MON_SLEEP can be
+ enabled together to get the stacktrace of both the waker
+ task and the wakee task.
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.c b/kernel/trace/rv/monitors/wakeup/wakeup.c
new file mode 100644
index 000000000000..01b47416f24e
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "wakeup"
+
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <trace/events/lock.h>
+#include <uapi/linux/futex.h>
+
+#include <rv_trace.h>
+#include <monitors/rtapp/rtapp.h>
+
+
+#ifndef __NR_futex
+#define __NR_futex (-__COUNTER__)
+#endif
+#ifndef __NR_futex_time64
+#define __NR_futex_time64 (-__COUNTER__)
+#endif
+
+#include "wakeup.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This includes "actual" real-time tasks and also PI-boosted
+ * tasks. A task being PI-boosted means it is blocking an "actual"
+ * real-task, therefore it should also obey the monitor's rule,
+ * otherwise the "actual" real-task may be delayed.
+ */
+ ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task));
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ ltl_atom_set(mon, LTL_WOKEN_BY_LOWER_PRIO, false);
+ ltl_atom_set(mon, LTL_WOKEN_BY_SOFTIRQ, false);
+
+ if (task_creation) {
+ ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
+ }
+
+ ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD));
+}
+
+static void handle_sched_waking(void *data, struct task_struct *task)
+{
+ if (in_task()) {
+ if (current->prio > task->prio)
+ ltl_atom_pulse(task, LTL_WOKEN_BY_LOWER_PRIO, true);
+ } else if (in_serving_softirq()) {
+ ltl_atom_pulse(task, LTL_WOKEN_BY_SOFTIRQ, true);
+ }
+}
+
+static void handle_contention_begin(void *data, void *lock, unsigned int flags)
+{
+ if (flags & LCB_F_RT)
+ ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+}
+
+static void handle_contention_end(void *data, void *lock, int ret)
+{
+ ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+}
+
+static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
+{
+ unsigned long args[6];
+ int op, cmd;
+
+ switch (id) {
+ case __NR_futex:
+ case __NR_futex_time64:
+ syscall_get_arguments(current, regs, args);
+ op = args[1];
+ cmd = op & FUTEX_CMD_MASK;
+
+ switch (cmd) {
+ case FUTEX_LOCK_PI:
+ case FUTEX_LOCK_PI2:
+ ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ break;
+ }
+ break;
+ }
+}
+
+static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
+{
+ ltl_atom_update(current, LTL_FUTEX_LOCK_PI, false);
+}
+
+static int enable_wakeup(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking);
+ rv_attach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin);
+ rv_attach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end);
+ rv_attach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter);
+ rv_attach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit);
+
+ return 0;
+}
+
+static void disable_wakeup(void)
+{
+ rv_detach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking);
+ rv_detach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin);
+ rv_detach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end);
+ rv_detach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter);
+ rv_detach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit);
+
+ ltl_monitor_destroy();
+}
+
+static struct rv_monitor rv_wakeup = {
+ .name = "wakeup",
+ .description = "Monitor that real-time tasks are not woken by lower-priority tasks",
+ .enable = enable_wakeup,
+ .disable = disable_wakeup,
+};
+
+static int __init register_wakeup(void)
+{
+ return rv_register_monitor(&rv_wakeup, &rv_rtapp);
+}
+
+static void __exit unregister_wakeup(void)
+{
+ rv_unregister_monitor(&rv_wakeup);
+}
+
+module_init(register_wakeup);
+module_exit(unregister_wakeup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
+MODULE_DESCRIPTION("Monitor that real-time tasks are not woken by lower-priority tasks");
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.h b/kernel/trace/rv/monitors/wakeup/wakeup.h
new file mode 100644
index 000000000000..6f80da64e0e1
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME wakeup
+
+enum ltl_atom {
+ LTL_BLOCK_ON_RT_MUTEX,
+ LTL_FUTEX_LOCK_PI,
+ LTL_RT,
+ LTL_USER_THREAD,
+ LTL_WOKEN_BY_LOWER_PRIO,
+ LTL_WOKEN_BY_SOFTIRQ,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "bl_on_rt_mu",
+ "fu_lo_pi",
+ "rt",
+ "us_th",
+ "wo_lo_pr",
+ "wo_so",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms);
+ bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
+ bool rt = test_bit(LTL_RT, mon->atoms);
+ bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
+ bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
+ bool val9 = block_on_rt_mutex || futex_lock_pi;
+ bool val6 = !woken_by_softirq;
+ bool val5 = !woken_by_lower_prio;
+ bool val8 = val5 && val6;
+ bool val10 = val8 || val9;
+ bool val3 = !user_thread;
+ bool val2 = !rt;
+ bool val4 = val2 || val3;
+ bool val11 = val4 || val10;
+
+ if (val11)
+ __set_bit(S0, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms);
+ bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms);
+ bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms);
+ bool rt = test_bit(LTL_RT, mon->atoms);
+ bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
+ bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
+ bool val9 = block_on_rt_mutex || futex_lock_pi;
+ bool val6 = !woken_by_softirq;
+ bool val5 = !woken_by_lower_prio;
+ bool val8 = val5 && val6;
+ bool val10 = val8 || val9;
+ bool val3 = !user_thread;
+ bool val2 = !rt;
+ bool val4 = val2 || val3;
+ bool val11 = val4 || val10;
+
+ switch (state) {
+ case S0:
+ if (val11)
+ __set_bit(S0, next);
+ break;
+ }
+}
diff --git a/kernel/trace/rv/monitors/wakeup/wakeup_trace.h b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h
new file mode 100644
index 000000000000..7e056183f920
--- /dev/null
+++ b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_WAKEUP
+DEFINE_EVENT(event_ltl_monitor_id, event_wakeup,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_wakeup,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_WAKEUP */
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index ee4e68102f17..29f155c6968b 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -164,7 +164,6 @@ struct dentry *get_monitors_root(void)
*/
LIST_HEAD(rv_monitors_list);
-static int task_monitor_count;
static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS];
int rv_get_task_monitor_slot(void)
@@ -173,22 +172,16 @@ int rv_get_task_monitor_slot(void)
lockdep_assert_held(&rv_interface_lock);
- if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
- return -EBUSY;
-
- task_monitor_count++;
-
for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) {
- if (task_monitor_slots[i] == false) {
+ if (!task_monitor_slots[i]) {
task_monitor_slots[i] = true;
return i;
}
}
- WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n");
-
- return -EINVAL;
+ return -EBUSY;
}
+EXPORT_SYMBOL_GPL(rv_get_task_monitor_slot);
void rv_put_task_monitor_slot(int slot)
{
@@ -199,12 +192,13 @@ void rv_put_task_monitor_slot(int slot)
return;
}
- WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n",
- slot);
+ if (WARN_ONCE(!task_monitor_slots[slot],
+ "RV releasing unused task monitor slot: %d\n", slot))
+ return;
- task_monitor_count--;
task_monitor_slots[slot] = false;
}
+EXPORT_SYMBOL_GPL(rv_put_task_monitor_slot);
/*
* Monitors with a parent are nested,
@@ -852,3 +846,69 @@ int __init rv_init_interface(void)
return 0;
}
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <rv/kunit.h>
+#include <kunit/visibility.h>
+
+/*
+ * rv_set_testing - ensure mutual exclusion between KUnit tests and real monitors
+ *
+ * KUnit tests for RV monitors rely on stubs that are incompatible with
+ * the execution of real monitors. Ensure mutual exclusion by acquiring
+ * the rv_interface_lock for the duration of the suite.
+ *
+ * Returns 0 on success, -EBUSY if any real monitor is already enabled.
+ */
+int rv_set_testing(struct kunit_suite *suite)
+{
+ struct rv_monitor *mon;
+
+ mutex_lock(&rv_interface_lock);
+
+ list_for_each_entry(mon, &rv_monitors_list, list) {
+ if (mon->enabled) {
+ mutex_unlock(&rv_interface_lock);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_set_testing);
+
+/*
+ * rv_clear_testing - allow real monitors to run again after KUnit tests
+ */
+void rv_clear_testing(struct kunit_suite *suite)
+{
+ mutex_unlock(&rv_interface_lock);
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing);
+
+/*
+ * rv_get_mock_current() is called only if we are running from a KUnit test.
+ * This can occur from a legitimate RV test or any unrelated test running when
+ * a real RV monitor is active and triggering events.
+ * We assume the former case is the only one where mock_current is not NULL and
+ * can occur only sequentially (KUnit doesn't run tests in parallel).
+ * We cannot rely on the test's context because there is no way to safely
+ * understand from which test we are running and KUnit utilities require
+ * locking, which is unsafe from NMI or scheduling context.
+ * Note that it is not possible for a real RV monitor to run when the RV KUnit
+ * tests are running (see rv_set_testing()).
+ */
+static struct task_struct *mock_current;
+
+void rv_mock_current(struct task_struct *tsk)
+{
+ mock_current = tsk;
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_mock_current);
+
+struct task_struct *rv_get_mock_current(void)
+{
+ return mock_current ?: current;
+}
+EXPORT_SYMBOL_GPL(rv_get_mock_current);
+#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
new file mode 100644
index 000000000000..3ad11195e664
--- /dev/null
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * RV monitor kunit tests:
+ * Tests the RV monitors by triggering fake events to verify monitor
+ * behavior and reactions. Tests start from the first defined event and
+ * trigger events in order to verify error detection.
+ */
+#include <rv/kunit.h>
+#include <kunit/test-bug.h>
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include "rv.h"
+
+/*
+ * An easy way to pass the context is to use kunit_get_current_test()->priv,
+ * but this doesn't always work (e.g. a reactor running from another context
+ * like softirq). Store the current value here whenever a test is running.
+ */
+static struct rv_kunit_ctx *active_ctx;
+
+__printf(1, 0)
+static void rv_kunit_mock_react(const char *msg, va_list args)
+{
+ if (active_ctx)
+ ++active_ctx->reactions;
+}
+
+/*
+ * teardown_test - Disable the monitor for a kunit test
+ *
+ * Since per-task monitors are special, make sure we reset all the ones we
+ * started manually here, if required.
+ */
+void teardown_test(void *arg)
+{
+ const struct rv_kunit_mon *mon = arg;
+ struct kunit *test = kunit_get_current_test();
+
+ if (test) {
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ if (mon->is_per_task && mon->task_reset) {
+ for (int i = 0; i < ctx->mock_task_count; i++)
+ mon->task_reset(ctx->mock_tasks[i]);
+ synchronize_rcu();
+ }
+ }
+
+ mon->rv_this->enabled = 0;
+
+ if (mon->rv_this->reactor)
+ mon->rv_this->react = mon->rv_this->reactor->react;
+ else
+ mon->rv_this->react = NULL;
+ active_ctx = NULL;
+ rv_mock_current(NULL);
+
+ if (mon->is_per_task)
+ *mon->task_slot = RV_PER_TASK_MONITOR_INIT;
+ else
+ mon->monitor_destroy();
+}
+
+/*
+ * prepare_test - Enable the monitor for a kunit test
+ *
+ * Do the bare minimum to set up the monitor, per-task monitors are special as
+ * "real" initialisation/destruction iterates over real tasks, and may register
+ * handlers. All we need is to select the right slot in the task_struct.
+ */
+void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon)
+{
+ KUNIT_ASSERT_FALSE(test, mon->rv_this->enabled);
+
+ active_ctx = test->priv;
+ mon->rv_this->react = rv_kunit_mock_react;
+
+ if (mon->is_per_task)
+ *mon->task_slot = 0;
+ else
+ KUNIT_ASSERT_EQ(test, mon->monitor_init(), 0);
+
+ mon->rv_this->enabled = 1;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ kunit_add_action_or_reset(test, teardown_test, (void *)mon));
+}
+
+struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ struct task_struct *tsk;
+
+ KUNIT_ASSERT_LT(test, ctx->mock_task_count, RV_KUNIT_MAX_MOCK_TASKS);
+
+ tsk = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk);
+
+ if (!IS_ENABLED(CONFIG_THREAD_INFO_IN_TASK)) {
+ tsk->stack = kunit_kzalloc(test, sizeof(struct thread_info), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk->stack);
+ }
+
+ ctx->mock_tasks[ctx->mock_task_count++] = tsk;
+ return tsk;
+}
+
+static int rv_mon_test_init(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+ test->priv = ctx;
+
+ return 0;
+}
+
+static void __maybe_unused rv_test_stub(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+
+/*
+ * rv_test_dummy - test reactions work as expected
+ */
+static void rv_test_dummy(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ static struct rv_monitor dummy_monitor = {
+ .name = "dummy",
+ .react = rv_kunit_mock_react,
+ };
+
+ active_ctx = ctx;
+
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_react(&dummy_monitor, "dummy");
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ active_ctx = NULL;
+}
+
+#include "monitors/sco/sco_kunit.c"
+#include "monitors/sssw/sssw_kunit.c"
+#include "monitors/sts/sts_kunit.c"
+#include "monitors/opid/opid_kunit.c"
+#include "monitors/nomiss/nomiss_kunit.c"
+#include "monitors/pagefault/pagefault_kunit.c"
+#include "monitors/sleep/sleep_kunit.c"
+
+static struct kunit_case rv_mon_test_cases[] = {
+ KUNIT_CASE(rv_test_dummy),
+ KUNIT_CASE(rv_test_sco),
+ KUNIT_CASE(rv_test_sssw),
+ KUNIT_CASE(rv_test_sts),
+ KUNIT_CASE(rv_test_opid),
+ KUNIT_CASE(rv_test_nomiss),
+ KUNIT_CASE(rv_test_pagefault),
+ KUNIT_CASE(rv_test_sleep),
+ {}
+};
+
+static struct kunit_suite rv_mon_test_suite = {
+ .name = "rv_mon",
+ .suite_init = rv_set_testing,
+ .suite_exit = rv_clear_testing,
+ .init = rv_mon_test_init,
+ .test_cases = rv_mon_test_cases,
+};
+
+kunit_test_suites(&rv_mon_test_suite);
+
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("RV monitor kunit tests: test monitors by triggering reactions");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/kernel/trace/rv/rv_reactors.c b/kernel/trace/rv/rv_reactors.c
index 460af07f7aba..2f5fc8d18dea 100644
--- a/kernel/trace/rv/rv_reactors.c
+++ b/kernel/trace/rv/rv_reactors.c
@@ -479,3 +479,4 @@ void rv_react(struct rv_monitor *monitor, const char *msg, ...)
va_end(args);
}
+EXPORT_SYMBOL_GPL(rv_react);
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 9622c269789c..2f8a932432c9 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -241,6 +241,7 @@ DECLARE_EVENT_CLASS(error_ltl_monitor_id,
);
#include <monitors/pagefault/pagefault_trace.h>
#include <monitors/sleep/sleep_trace.h>
+#include <monitors/wakeup/wakeup_trace.h>
// Add new monitors based on CONFIG_LTL_MON_EVENTS_ID here
#endif /* CONFIG_LTL_MON_EVENTS_ID */
diff --git a/tools/testing/selftests/verification/test.d/rv_deadline.tc b/tools/testing/selftests/verification/test.d/rv_deadline.tc
new file mode 100644
index 000000000000..fc95267dbb82
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_deadline.tc
@@ -0,0 +1,23 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test deadline monitors trigger no reaction
+# requires: available_reactors deadline:monitor printk:reactor stress-ng:program
+
+load() { # returns true if there was a reaction
+ local lines_before
+ lines_before=$(dmesg | wc -l)
+ stress-ng --cpu 2 --sched deadline --sched-period 100000000 \
+ --sched-deadline 100000000 --sched-runtime 20000000 -t 5 &
+ stress-ng --cpu 2 --sched rr --sched-prio 50 --cyclic 1 \
+ --cyclic-policy rr --cyclic-prio 50 -t 5 &
+ wait
+ dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor [a-z]\+ does not allow event"
+}
+
+echo 1 > monitors/deadline/enable
+echo printk > monitors/deadline/reactors
+
+! load || false
+
+echo nop > monitors/deadline/reactors
+echo 0 > monitors/deadline/enable
diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
index f29236defb5a..61e2c8b54d9a 100644
--- a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
+++ b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
@@ -10,7 +10,7 @@ test_simple_monitor() {
grep -q "$monitor$" enabled_monitors
echo 0 > "monitors/$prefix$monitor/enable"
- ! grep -q "$monitor$" enabled_monitors
+ ! grep -q "$monitor$" enabled_monitors || false
echo "$monitor" >> enabled_monitors
grep -q 1 "monitors/$prefix$monitor/enable"
@@ -34,12 +34,12 @@ test_container_monitor() {
test -n "$nested"
echo 0 > "monitors/$monitor/enable"
- ! grep -q "^$monitor$" enabled_monitors
+ ! grep -q "^$monitor$" enabled_monitors || false
for nested_dir in "monitors/$monitor"/*; do
[ -d "$nested_dir" ] || continue
nested=$(basename "$nested_dir")
- ! grep -q "^$monitor:$nested$" enabled_monitors
+ ! grep -q "^$monitor:$nested$" enabled_monitors || false
done
echo "$monitor" >> enabled_monitors
@@ -71,5 +71,5 @@ for monitor_dir in monitors/*; do
fi
done
-! echo non_existent_monitor > enabled_monitors
-! grep -q "^non_existent_monitor$" enabled_monitors
+! echo non_existent_monitor > enabled_monitors || false
+! grep -q "^non_existent_monitor$" enabled_monitors || false
diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
index 2958bf849338..516a20971390 100644
--- a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
+++ b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
@@ -64,5 +64,5 @@ done
monitor=$(ls /sys/kernel/tracing/rv/monitors -1 | head -n 1)
test -f "monitors/$monitor/reactors"
-! echo non_existent_reactor > "monitors/$monitor/reactors"
-! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors"
+! echo non_existent_reactor > "monitors/$monitor/reactors" || false
+! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors" || false
diff --git a/tools/testing/selftests/verification/test.d/rv_stall.tc b/tools/testing/selftests/verification/test.d/rv_stall.tc
new file mode 100644
index 000000000000..515a10263ca1
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_stall.tc
@@ -0,0 +1,33 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test stall monitor
+# requires: available_reactors stall:monitor printk:reactor stress-ng:program
+
+THRESHOLD=/sys/module/stall/parameters/threshold_jiffies
+ORIG_THRESHOLD=$(cat $THRESHOLD)
+trap 'echo $ORIG_THRESHOLD > $THRESHOLD' EXIT
+
+load() { # returns true if there was a reaction
+ local lines_before cpu
+ cpu=$(($(nproc) - 1))
+ lines_before=$(dmesg | wc -l)
+ stress-ng --cpu 1 --taskset "$cpu" --sched rr --sched-prio 1 -t 3 &
+ stress-ng --cpu 5 --taskset "$cpu" -t 3 &
+ wait
+ dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor stall does not allow event"
+}
+
+echo 5000 > $THRESHOLD
+echo 1 > monitors/stall/enable
+echo printk > monitors/stall/reactors
+
+! load || false
+
+echo 0 > monitors/stall/enable
+echo 70 > $THRESHOLD
+echo 1 > monitors/stall/enable
+
+load
+
+echo nop > monitors/stall/reactors
+echo 0 > monitors/stall/enable
diff --git a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
index 5a59432b1d93..17e1edfb3902 100644
--- a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
+++ b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
@@ -4,11 +4,29 @@
# requires: available_reactors wwnr:monitor printk:reactor stress-ng:program
load() { # returns true if there was a reaction
- local lines_before num
+ local lines_before num load_pid ret
num=$((($(nproc) + 1) / 2))
lines_before=$(dmesg | wc -l)
- stress-ng --cpu-sched "$num" --timer "$num" -t 5 -q
- dmesg | tail -n $((lines_before + 1)) | grep -q "rv: monitor wwnr does not allow event"
+ stress-ng --cpu-sched "$num" --timer "$num" -t 5 -q &
+ load_pid=$!
+ timeout 5 dmesg -w | tail -n +$((lines_before + 1)) | \
+ grep -m 1 -q "rv: monitor wwnr does not allow event"
+ ret=$?
+ kill "$load_pid" || true
+ wait "$load_pid" || true
+ return $ret
+}
+
+# loads may flood the ringbuffer, wait for all pending printks (timeout at 2 minutes)
+wait_dmesg_flush() {
+ local last_before last_after
+ for _ in $(seq 400); do
+ last_before=$last_after
+ last_after=$(dmesg | grep "rv:" | tail -n 1 || true)
+ [ "$last_before" = "$last_after" ] && return 0
+ sleep .3
+ done
+ return 1
}
echo 1 > monitors/wwnr/enable
@@ -17,13 +35,17 @@ echo printk > monitors/wwnr/reactors
load
echo 0 > monitoring_on
-! load
+wait_dmesg_flush
+
+! load || false
echo 1 > monitoring_on
load
echo 0 > reacting_on
-! load
+wait_dmesg_flush
+
+! load || false
echo 1 > reacting_on
echo nop > monitors/wwnr/reactors
diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl
index 6f26c4810f78..4d78fdd204c0 100644
--- a/tools/verification/models/rtapp/sleep.ltl
+++ b/tools/verification/models/rtapp/sleep.ltl
@@ -1,7 +1,7 @@
-RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST))
+RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST))
-RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD)
- and ((not WAKE) until RT_FRIENDLY_WAKE)
+RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON
+ and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE)
RT_VALID_SLEEP_REASON = FUTEX_WAIT
or RT_FRIENDLY_NANOSLEEP
@@ -9,15 +9,12 @@ RT_VALID_SLEEP_REASON = FUTEX_WAIT
RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP
and NANOSLEEP_TIMER_ABSTIME
- and (NANOSLEEP_CLOCK_MONOTONIC or NANOSLEEP_CLOCK_TAI)
+ and not NANOSLEEP_CLOCK_REALTIME
RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO
or WOKEN_BY_HARDIRQ
or WOKEN_BY_NMI
or ABORT_SLEEP
- or KTHREAD_SHOULD_STOP
ALLOWLIST = BLOCK_ON_RT_MUTEX
or FUTEX_LOCK_PI
- or TASK_IS_RCU
- or TASK_IS_MIGRATION
diff --git a/tools/verification/models/rtapp/wakeup.ltl b/tools/verification/models/rtapp/wakeup.ltl
new file mode 100644
index 000000000000..a5d63ca0811a
--- /dev/null
+++ b/tools/verification/models/rtapp/wakeup.ltl
@@ -0,0 +1,5 @@
+RULE = always (((RT and USER_THREAD) imply
+ (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST))
+
+ALLOWLIST = BLOCK_ON_RT_MUTEX
+ or FUTEX_LOCK_PI
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 5b898360ba48..8ae5fc0d1d17 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -78,4 +78,7 @@ clean: doc_clean fixdep-clean
$(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-*
$(Q)rm -rf feature
-.PHONY: FORCE clean
+check: $(RV)
+ RV=$(RV) prove -o --directives -f tests/
+
+.PHONY: FORCE clean check
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index b8fe24a87d97..09e0d8598619 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -50,23 +50,23 @@ static void rv_list(int argc, char **argv)
" [container]: list only monitors in this container",
NULL,
};
- int i, print_help = 0, retval = 0;
+ int i, print_help = 0, retval = EXIT_SUCCESS;
char *container = NULL;
if (argc == 2) {
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
print_help = 1;
- retval = 0;
+ retval = EXIT_SUCCESS;
} else if (argv[1][0] == '-') {
/* assume invalid option */
print_help = 1;
- retval = 1;
+ retval = EXIT_FAILURE;
} else
container = argv[1];
} else if (argc > 2) {
/* more than 2 is always usage */
print_help = 1;
- retval = 1;
+ retval = EXIT_FAILURE;
}
if (print_help) {
fprintf(stderr, "rv version %s\n", VERSION);
@@ -77,7 +77,7 @@ static void rv_list(int argc, char **argv)
ikm_list_monitors(container);
- exit(0);
+ exit(EXIT_SUCCESS);
}
/*
@@ -108,14 +108,14 @@ static void rv_mon(int argc, char **argv)
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
- exit(1);
+ exit(EXIT_FAILURE);
} else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "rv version %s\n", VERSION);
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
- exit(0);
+ exit(EXIT_SUCCESS);
}
monitor_name = argv[1];
@@ -127,7 +127,7 @@ static void rv_mon(int argc, char **argv)
if (!run)
err_msg("rv: monitor %s does not exist\n", monitor_name);
- exit(!run);
+ exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
static void usage(int exit_val, const char *fmt, ...)
@@ -174,13 +174,13 @@ static void usage(int exit_val, const char *fmt, ...)
int main(int argc, char **argv)
{
if (geteuid())
- usage(1, "%s needs root permission", argv[0]);
+ usage(EXIT_FAILURE, "%s needs root permission", argv[0]);
if (argc <= 1)
- usage(1, "%s requires a command", argv[0]);
+ usage(EXIT_FAILURE, "%s requires a command", argv[0]);
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
- usage(0, "help");
+ usage(EXIT_SUCCESS, "help");
if (!strcmp(argv[1], "list"))
rv_list(--argc, &argv[1]);
@@ -197,5 +197,5 @@ int main(int argc, char **argv)
}
/* invalid sub-command */
- usage(1, "%s does not know the %s command, old version?", argv[0], argv[1]);
+ usage(EXIT_FAILURE, "%s does not know the %s command, old version?", argv[0], argv[1]);
}
diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t
new file mode 100644
index 000000000000..201af33a52cc
--- /dev/null
+++ b/tools/verification/rv/tests/rv_list.t
@@ -0,0 +1,48 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify help page" \
+ "$RV --help" 0 "usage: rv command"
+
+check "verify list subcommand help" \
+ "$RV list --help" 0 "list all available monitors"
+
+all_nested=$(grep : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+all_non_nested=$(grep -v : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+sched_monitors=$(grep sched: $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+description_state="[[:space:]]\+[[:print:]]\+\[\(OFF\|ON\)\]"
+line_nested=" - \($all_nested\)${description_state}"
+line_non_nested="\($all_non_nested\)${description_state}"
+
+# List monitors and containers
+check "list all monitors" \
+ "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$"
+
+check_if_exists "list container" \
+ "$RV list sched" "$RVDIR/monitors/sched" \
+ "" "-- No monitor found in container sched --" \
+ "^\($sched_monitors\)${description_state}$"
+
+check_if_exists "list non-container" \
+ "$RV list wwnr" "$RVDIR/monitors/wwnr" \
+ "-- No monitor found in container wwnr --" \
+ "^\( - \)\?[[:alnum:]]\+${description_state}$"
+
+check "list incomplete container name" \
+ "$RV list s" 0 "-- No monitor found in container s --"
+
+# Error handling tests
+check "no command" \
+ "$RV" 1 "rv requires a command"
+
+check "invalid command" \
+ "$RV invalid" 1 "rv does not know the invalid command"
+
+test_end
diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
new file mode 100644
index 000000000000..cbc346c74c71
--- /dev/null
+++ b/tools/verification/rv/tests/rv_mon.t
@@ -0,0 +1,95 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify mon subcommand help" \
+ "$RV mon --help" 0 "run a monitor"
+
+# Error handling tests
+check "mon without monitor name" \
+ "$RV mon" 1 "usage: rv mon"
+
+check "invalid monitor name" \
+ "$RV mon invalid" 1 "monitor invalid does not exist"
+
+if [ -d $RVDIR/monitors/wwnr ]; then
+
+check "invalid reactor name" \
+ "$RV mon wwnr -r invalid" 1 "failed to set invalid reactor, is it available?"
+
+check "monitor name is substring of another monitor" \
+ "$RV mon nr" 1 "monitor nr does not exist"
+
+check "already enabled monitor returns error" \
+ "echo 1 > $RVDIR/monitors/wwnr/enable; $RV mon wwnr" 1 \
+ "monitor wwnr (in-kernel) is already enabled"
+echo 0 > $RVDIR/monitors/wwnr/enable
+
+fi
+
+# rv mon runs until terminated
+set_expected_timeout 2s
+
+# Run monitors with different configurations
+check_if_exists "run the monitor without parameters" \
+ "$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "."
+
+check_if_exists "run the monitor as verbose" \
+ "$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \
+ "my pid is \$pid" "\(event\|error\)"
+
+check_if_exists "run the monitor with a reactor" \
+ "$RV mon wwnr -r printk & sleep .5 && cat $RVDIR/monitors/wwnr/reactors && wait" \
+ "$RVDIR/monitors/wwnr/reactors" "\[printk\]"
+
+check_if_exists "reactor is restored after exit" \
+ "cat $RVDIR/monitors/wwnr/reactors" \
+ "$RVDIR/monitors/wwnr/reactors" "\[nop\]"
+
+check_if_exists "run a nested monitor with a reactor" \
+ "$RV mon snroc -r printk & sleep .5 && cat $RVDIR/monitors/sched/snroc/reactors && wait" \
+ "$RVDIR/monitors/sched/snroc/reactors" "\[printk\]"
+
+check_if_exists "run an explicitly nested monitor with a reactor" \
+ "$RV mon sched:sssw -r printk & sleep .5 && cat $RVDIR/monitors/sched/sssw/reactors && wait" \
+ "$RVDIR/monitors/sched/sssw/reactors" "\[printk\]"
+
+check_if_exists "run container monitor" \
+ "$RV mon sched & sleep .5 && cat $RVDIR/monitors/sched/{sssw,sco}/enable && wait" \
+ "$RVDIR/monitors/sched" "1" "0" "^1$"
+
+# Regexes for the trace
+header="^[[:space:]]\+\(\([][A-Z_x<>-]\+\||\)[[:space:]]*\)\+$"
+type="\(event\|error\)[[:space:]]\+"
+genpid="[0-9]\+[[:space:]]\+"
+selfpid="\$pid[[:space:]]\+"
+cpu="\[[0-9]\{3\}\][[:space:]]\+"
+state="[a-z_]\+ "
+trace_task="${genpid}${cpu}${type}${genpid}${state}"
+trace_task_self="${genpid}${cpu}${type}${selfpid}${state}"
+trace_cpu="${genpid}${cpu}${type}${state}"
+trace_cpu_self="${selfpid}${cpu}${type}${state}"
+
+check_if_exists "run per-task monitor with tracing" \
+ "$RV mon sssw -t" "$RVDIR/monitors/sched/sssw" \
+ "$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task monitor tracing also self" \
+ "$RV mon sched:sssw -t -s" "$RVDIR/monitors/sched/sssw" \
+ "$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu monitor with tracing" \
+ "$RV mon sched:sco -t" "$RVDIR/monitors/sched/sco" \
+ "$header" "$trace_cpu_self" "\($header\|$trace_cpu\)"
+
+check_if_exists "run per-cpu monitor tracing also self" \
+ "$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \
+ "$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
+test_end
diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile
index cfc4056c1e87..48d0376a5cc4 100644
--- a/tools/verification/rvgen/Makefile
+++ b/tools/verification/rvgen/Makefile
@@ -13,12 +13,17 @@ all:
.PHONY: clean
clean:
+.PHONY: check
+check:
+ prove -o --directives -f tests/
+
.PHONY: install
install:
$(INSTALL) rvgen/automata.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/automata.py
$(INSTALL) rvgen/dot2c.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2c.py
$(INSTALL) dot2c -D -m 755 $(DESTDIR)$(bindir)/
$(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py
+ $(INSTALL) rvgen/kunit.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/kunit.py
$(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py
$(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py
$(INSTALL) rvgen/ltl2ba.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2ba.py
diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index 5c923dc10d0f..246b43fa29f1 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -6,14 +6,16 @@
# dot2k: transform dot files into a monitor for the Linux kernel.
#
# For further information, see:
-# Documentation/trace/rv/da_monitor_synthesis.rst
+# Documentation/trace/rv/monitor_synthesis.rst
if __name__ == '__main__':
from rvgen.dot2k import da2k, ha2k
from rvgen.generator import Monitor
from rvgen.container import Container
from rvgen.ltl2k import ltl2k
+ from rvgen.kunit import KUnit, KUnitError
from rvgen.automata import AutomataError
+ from rvgen.ltl2ba import LTLError
import argparse
import sys
@@ -41,6 +43,11 @@ if __name__ == '__main__':
container_parser = subparsers.add_parser("container", parents=[parent_parser])
container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
+ kunit_parser = subparsers.add_parser("kunit", parents=[parent_parser])
+ kunit_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
+ kunit_parser.add_argument('-l', "--local", dest="local", action="store_true", required=False,
+ help="Force looking for the monitor in the current directory only")
+
params = parser.parse_args()
try:
@@ -55,10 +62,17 @@ if __name__ == '__main__':
else:
print("Unknown monitor class:", params.monitor_class)
sys.exit(1)
- else:
+ elif params.subcmd == "container":
monitor = Container(vars(params))
- except AutomataError as e:
- print(f"There was an error processing {params.spec}: {e}", file=sys.stderr)
+ elif params.subcmd == "kunit":
+ monitor = KUnit(vars(params))
+ monitor.print_files()
+ sys.exit(0)
+ except (AutomataError, LTLError) as e:
+ print(f"There was an error processing {params.spec}:\n{e}", file=sys.stderr)
+ sys.exit(1)
+ except KUnitError as e:
+ print(f"There was an error generating KUnit files:\n{e}", file=sys.stderr)
sys.exit(1)
print(f"Writing the monitor into the directory {monitor.name}")
diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index b9f8149f7118..fd37ce304276 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -9,22 +9,349 @@
# Documentation/trace/rv/deterministic_automata.rst
import ntpath
-import re
-from typing import Iterator
-from itertools import islice
-class _ConstraintKey:
- """Base class for constraint keys."""
+import lark
-class _StateConstraintKey(_ConstraintKey, int):
- """Key for a state constraint. Under the hood just state_id."""
- def __new__(cls, state_id: int):
- return super().__new__(cls, state_id)
+class ParseTree:
+ # based on https://graphviz.org/doc/info/lang.html
+ # with the irrelevant stuffs (port and compass) removed
+ grammar = r'''
+ start: "strict"? ("graph" | "digraph") ID? "{" stmt_list "}"
-class _EventConstraintKey(_ConstraintKey, tuple):
- """Key for an event constraint. Under the hood just tuple(state_id,event_id)."""
- def __new__(cls, state_id: int, event_id: int):
- return super().__new__(cls, (state_id, event_id))
+ stmt_list: (stmt ";"? stmt_list)?
+
+ stmt: node_stmt
+ | edge_stmt
+ | attr_stmt
+ | ID "=" ID
+ | subgraph
+
+ attr_stmt: attr_type attr_list
+
+ attr_type: "graph" -> graph
+ | "node" -> node
+ | "edge" -> edge
+
+ attr_list: "[" a_list? "]" attr_list?
+
+ a_list: ID "=" ID (";" | ",")? a_list?
+
+ edge_stmt: (node_id | subgraph) edgerhs attr_list?
+
+ edgerhs: edgeop (node_id | subgraph) edgerhs?
+
+ edgeop: "->" | "--"
+
+ node_stmt: node_id attr_list?
+
+ node_id: ID
+
+ subgraph: ("subgraph" ID?)? "{" stmt_list "}"
+
+ ID: CNAME
+ | /-?(\.[0-9]+|[0-9]+(\.[0-9]*))/
+ | ESCAPED_STRING
+
+ %import common.CNAME
+ %import common.ESCAPED_STRING
+ %import common.WS
+ %ignore WS
+ '''
+
+ @staticmethod
+ def parse_edge(tree: lark.Tree) -> tuple[str, str]:
+ # only support a simple node-to-node edge
+ nodes = []
+ for node in tree.iter_subtrees_topdown():
+ if node.data == "node_id":
+ nodes.append(node.children[0].strip('"'))
+
+ if len(nodes) != 2:
+ raise AutomataError("Only state-to-state transition is supported")
+
+ return tuple(nodes)
+
+ class ParseNodes(lark.visitors.Visitor):
+ def __init__(self, *args, **kwargs):
+ self.nodes = set()
+ super().__init__(*args, **kwargs)
+
+ def node_stmt(self, tree):
+ node_id = tree.children[0]
+ node = node_id.children[0].strip('"')
+ self.nodes.add(node)
+
+ class ParseEdges(lark.visitors.Visitor):
+ def __init__(self, *args, **kwargs):
+ self.edges = set()
+ super().__init__(*args, **kwargs)
+
+ def edge_stmt(self, tree):
+ edge = ParseTree.parse_edge(tree)
+ self.edges.add(edge)
+
+ class ParseAttributes(lark.visitors.Interpreter):
+ def __init__(self, *args, **kwargs):
+ '''
+ Stacks of default attributes. [0] is the default
+ attributes for the outermost scope, while [-1] is the
+ default attributes for the current scope.
+ '''
+ self.default_node_attrs = [{}]
+ self.default_edge_attrs = [{}]
+
+ self.node_attrs = {}
+ self.edge_attrs = {}
+
+ super().__init__(*args, **kwargs)
+
+ @staticmethod
+ def __get_attrs(stmt: lark.Tree) -> dict[str, str]:
+ attrs = {}
+
+ for node in stmt.iter_subtrees():
+ if node.data == "a_list":
+ attrs[node.children[0]] = node.children[1].strip('"')
+
+ return attrs
+
+
+ def subgraph(self, tree):
+ # We are entering a new scope, inherit the default
+ # attributes of the outer scope
+ self.default_node_attrs.append(self.default_node_attrs[-1].copy())
+ self.default_edge_attrs.append(self.default_edge_attrs[-1].copy())
+
+ children = self.visit_children(tree)
+
+ # Exiting the scope
+ del self.default_node_attrs[-1]
+ del self.default_edge_attrs[-1]
+
+ return children
+
+ def node_stmt(self, tree):
+ node_id = tree.children[0]
+ node = node_id.children[0].strip('"')
+
+ attrs = self.default_node_attrs[-1].copy()
+ attrs |= self.__get_attrs(tree)
+
+ if attrs:
+ if node in self.node_attrs:
+ self.node_attrs[node] = attrs | self.node_attrs[node]
+ else:
+ self.node_attrs[node] = attrs
+
+ return self.visit_children(tree)
+
+ def edge_stmt(self, tree):
+ edge = ParseTree.parse_edge(tree)
+
+ attrs = self.default_edge_attrs[-1].copy()
+ attrs |= self.__get_attrs(tree)
+
+ if attrs:
+ if edge in self.edge_attrs:
+ self.edge_attrs[edge] = attrs | self.edge_attrs[edge]
+ else:
+ self.edge_attrs[edge] = attrs
+
+ return self.visit_children(tree)
+
+ def attr_stmt(self, tree):
+ attr_type = tree.children[0].data
+ attrs = self.__get_attrs(tree)
+
+ if attr_type == "node":
+ self.default_node_attrs[-1] |= attrs
+ elif attr_type == "edge":
+ self.default_edge_attrs[-1] |= attrs
+ else:
+ # graph attributes are irrelevant
+ pass
+
+ self.visit_children(tree)
+
+ def __init__(self, dot_file):
+ parser = lark.Lark(self.grammar, parser='lalr')
+ node_parser = self.ParseNodes()
+ edge_parser = self.ParseEdges()
+ attributes_parser = self.ParseAttributes()
+
+ try:
+ with open(dot_file, "r") as f:
+ tree = parser.parse(f.read())
+ attributes_parser.visit(tree)
+ node_parser.visit(tree)
+ edge_parser.visit(tree)
+ except OSError as exc:
+ raise AutomataError(exc.strerror) from exc
+ except lark.exceptions.UnexpectedInput as exc:
+ raise AutomataError(str(exc))
+
+ self.nodes = node_parser.nodes
+ self.edges = edge_parser.edges
+ self.node_attrs = attributes_parser.node_attrs
+ self.edge_attrs = attributes_parser.edge_attrs
+
+class ConstraintCondition:
+ def __init__(self, env: str, op: str, val: str, unit=None):
+ self.env = env
+ self.op = op
+ self.val = val
+ self.unit = unit
+ if unit is None:
+ # try to infer unit from constants or parameters
+ val_for_unit = val.lower().replace("()", "")
+ if val_for_unit.endswith("_ns"):
+ self.unit = "ns"
+ if val_for_unit.endswith("_jiffies"):
+ self.unit = "j"
+
+class ConstraintRule:
+ grammar = r'''
+ rule: condition (OP condition)*
+
+ OP: "&&" | "||"
+
+ condition: ENV CMP_OP VAL UNIT?
+
+ ENV: CNAME
+
+ CMP_OP: "==" | "!=" | "<=" | "<" | ">=" | ">"
+
+ VAL: /[0-9]+/
+ | /[A-Z_]+\(\)/
+ | /[A-Z_]+/
+ | /[a-z_]+\(\)/
+ | /[a-z_]+/
+
+ UNIT: "ns" | "us" | "ms" | "s" | "j"
+ '''
+
+ def __init__(self, c: ConstraintCondition):
+ '''
+ A list of pairs of
+ - the condition (e.g. is_constr_dl == 1)
+ - the logical operator ("||" or "&&") combining this
+ condition with the next one if it exists, otherwise None
+
+ TODO: Perhaps use an abstract syntax tree instead, because
+ this representation cannot capture precedence
+ '''
+ self.rules = [[c, None]]
+
+ def chain(self, op: str, c: ConstraintCondition):
+ self.rules[-1][1] = op
+ self.rules.append([c, None])
+
+class ConstraintReset:
+ def __init__(self, env):
+ self.env = env
+
+class StateLabelParser:
+ grammar = r'''
+ label: CNAME ("\\n" condition)?
+
+ %import common.CNAME
+ %import common.WS
+ %ignore WS
+ ''' + ConstraintRule.grammar
+
+ parser = lark.Lark(grammar, parser='lalr', start="label")
+
+ def __init__(self, label: str):
+ try:
+ tree = self.parser.parse(label)
+ except lark.exceptions.UnexpectedInput as exc:
+ raise(AutomataError(f"Unrecognised state \"{label}\"\n{exc}"))
+
+ self.state = tree.children[0]
+ self.constraint = None
+
+ if len(tree.children) == 2:
+ self.constraint = ConstraintCondition(*tree.children[1].children)
+ if self.constraint.op not in ("<", "<="):
+ raise AutomataError("State constraints must be clock expirations like"
+ f" clk<N ({label})")
+
+class EventLabelParser:
+ grammar = r'''
+ events: event ("\\n" event)*
+
+ event: name (";" guard)?
+
+ guard: reset
+ | rule
+ | rule ";" reset
+ | reset ";" rule
+
+ name: CNAME
+
+ reset: "reset" "(" ENV ")"
+
+ %import common.CNAME
+ %import common.WS
+ %ignore WS
+ ''' + ConstraintRule.grammar
+
+ parser = lark.Lark(grammar, parser='lalr', start="events")
+
+ class GetEvents(lark.visitors.Transformer):
+ def guard(self, args):
+ reset = None
+ rule = None
+ for arg in args:
+ if arg.data == "reset":
+ reset = ConstraintReset(arg.children[0])
+ elif arg.data == "rule":
+ conditions = arg.children
+ rule = ConstraintRule(conditions[0])
+ for i in range(1, len(conditions), 2):
+ rule.chain(conditions[i], conditions[i + 1])
+ return reset, rule
+
+ def OP(self, args):
+ return args
+
+ def condition(self, args):
+ return ConstraintCondition(*args)
+
+ def event(self, args):
+ assert(len(args) <= 2)
+ name = args[0]
+ rule, reset = None, None
+ if len(args) == 2:
+ reset, rule = args[1]
+ return name, reset, rule
+
+ def events(self, args):
+ return args
+
+ def name(self, args):
+ return args[0]
+
+ def __init__(self, label: str):
+ try:
+ tree = self.parser.parse(label)
+ self.events = self.GetEvents().transform(tree)
+ except lark.exceptions.UnexpectedInput as exc:
+ raise(AutomataError(f"Unrecognised event \"{label}\"\n{exc}"))
+
+class Transition:
+ def __init__(self, src: str, dst: str, event: str,
+ reset: ConstraintReset, rule: ConstraintRule):
+ self.src = src
+ self.dst = dst
+ self.event = event
+ self.rule = rule
+ self.reset = reset
+
+class State:
+ def __init__(self, name: str, inv: ConstraintCondition):
+ self.name = name
+ self.inv = inv
class AutomataError(Exception):
"""Exception raised for errors in automata parsing and validation.
@@ -44,35 +371,19 @@ class Automata:
invalid_state_str = "INVALID_STATE"
init_marker = "__init_"
- node_marker = "{node"
- # val can be numerical, uppercase (constant or macro), lowercase (parameter or function)
- # only numerical values should have units
- constraint_rule = re.compile(r"""
- ^
- (?P<env>[a-zA-Z_][a-zA-Z0-9_]+) # C-like identifier for the env var
- (?P<op>[!<=>]{1,2}) # operator
- (?P<val>
- [0-9]+ | # numerical value
- [A-Z_]+\(\) | # macro
- [A-Z_]+ | # constant
- [a-z_]+\(\) | # function
- [a-z_]+ # parameter
- )
- (?P<unit>[a-z]{1,2})? # optional unit for numerical values
- """, re.VERBOSE)
- constraint_reset = re.compile(r"^reset\((?P<env>[a-zA-Z_][a-zA-Z0-9_]+)\)")
def __init__(self, file_path, model_name=None):
self.__dot_path = file_path
self.name = model_name or self.__get_model_name()
- self.__dot_lines = self.__open_dot()
- self.states, self.initial_state, self.final_states = self.__get_state_variables()
+ self.__parse_tree = ParseTree(file_path)
+ self.transitions = self.__parse_transitions()
+ self.states, self.initial_state, self.final_states = self.__parse_states()
self.env_types = {}
self.env_stored = set()
self.constraint_vars = set()
self.self_loop_reset_events = set()
self.events, self.envs = self.__get_event_variables()
- self.function, self.constraints = self.__create_matrix()
+ self.function = self.__create_matrix()
self.events_start, self.events_start_run = self.__store_init_events()
self.env_stored = sorted(self.env_stored)
self.constraint_vars = sorted(self.constraint_vars)
@@ -90,194 +401,94 @@ class Automata:
return model_name
- def __open_dot(self) -> list[str]:
- dot_lines = []
- try:
- with open(self.__dot_path) as dot_file:
- dot_lines = dot_file.readlines()
- except OSError as exc:
- raise AutomataError(exc.strerror) from exc
+ def __parse_transitions(self):
+ transitions = []
- if not dot_lines:
- raise AutomataError(f"{self.__dot_path} is empty")
+ for edge in self.__parse_tree.edges:
+ attr = self.__parse_tree.edge_attrs.get(edge)
+ if not attr:
+ continue
- # checking the first line:
- line = dot_lines[0].split()
+ label = attr.get("label")
- if len(line) < 2 or line[0] != "digraph" or line[1] != "state_automaton":
- raise AutomataError(f"Not a valid .dot format: {self.__dot_path}")
+ src, dst = edge
- return dot_lines
+ parser = EventLabelParser(label)
+ for event, reset, rule in parser.events:
+ transitions.append(Transition(src, dst, event, reset, rule))
- def __get_cursor_begin_states(self) -> int:
- for cursor, line in enumerate(self.__dot_lines):
- split_line = line.split()
+ transitions.sort(key=lambda t : (t.src, t.event))
+ return transitions
- if len(split_line) and split_line[0] == self.node_marker:
- return cursor
+ def __parse_states(self):
+ initial_state = ""
+ states = []
+ final_states = []
- raise AutomataError("Could not find a beginning state")
+ for node in self.__parse_tree.nodes:
+ attr = self.__parse_tree.node_attrs[node]
+ label = attr.get("label")
- def __get_cursor_begin_events(self) -> int:
- state = 0
- cursor = 0 # make pyright happy
+ if node.startswith(Automata.init_marker):
+ initial_state = node[len(Automata.init_marker):]
- for cursor, line in enumerate(self.__dot_lines):
- line = line.split()
- if not line:
+ if not label:
continue
- if state == 0:
- if line[0] == self.node_marker:
- state = 1
- elif line[0] != self.node_marker:
- break
- else:
- raise AutomataError("Could not find beginning event")
-
- cursor += 1 # skip initial state transition
- if cursor == len(self.__dot_lines):
- raise AutomataError("Dot file ended after event beginning")
-
- return cursor
-
- def __get_state_variables(self) -> tuple[list[str], str, list[str]]:
- # wait for node declaration
- states = []
- final_states = []
- initial_state = ""
-
- has_final_states = False
- cursor = self.__get_cursor_begin_states()
-
- # process nodes
- for line in islice(self.__dot_lines, cursor, None):
- split_line = line.split()
- if not split_line or split_line[0] != self.node_marker:
- break
+ parser = StateLabelParser(label)
+ state = State(parser.state, parser.constraint)
- raw_state = split_line[-1]
+ states.append(state)
- # "enabled_fired"}; -> enabled_fired
- state = raw_state.replace('"', '').replace('};', '').replace(',', '_')
- if state.startswith(self.init_marker):
- initial_state = state[len(self.init_marker):]
- else:
- states.append(state)
- if "doublecircle" in line:
- final_states.append(state)
- has_final_states = True
+ shape = attr.get("shape")
+ if shape in ("doublecircle", "ellipse"):
+ final_states.append(state)
- if "ellipse" in line:
- final_states.append(state)
- has_final_states = True
+ initial_state = next((s for s in states if s.name == initial_state), None)
if not initial_state:
raise AutomataError("The automaton doesn't have an initial state")
- states = sorted(set(states))
- states.remove(initial_state)
-
- # Insert the initial state at the beginning of the states
- states.insert(0, initial_state)
-
- if not has_final_states:
+ if not final_states:
final_states.append(initial_state)
+ states.remove(initial_state)
+ states.sort(key=lambda s : s.name)
+ states.insert(0, initial_state)
return states, initial_state, final_states
def __get_event_variables(self) -> tuple[list[str], list[str]]:
events: list[str] = []
envs: list[str] = []
- # here we are at the begin of transitions, take a note, we will return later.
- cursor = self.__get_cursor_begin_events()
-
- for line in map(str.lstrip, islice(self.__dot_lines, cursor, None)):
- if not line.startswith('"'):
- break
-
- # transitions have the format:
- # "all_fired" -> "both_fired" [ label = "disable_irq" ];
- # ------------ event is here ------------^^^^^
- split_line = line.split()
- if len(split_line) > 1 and split_line[1] == "->":
- event = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '')
-
- # when a transition has more than one label, they are like this
- # "local_irq_enable\nhw_local_irq_enable_n"
- # so split them.
-
- for i in event.split("\\n"):
- # if the event contains a constraint (hybrid automata),
- # it will be separated by a ";":
- # "sched_switch;x<1000;reset(x)"
- ev, *constr = i.split(";")
- if constr:
- if len(constr) > 2:
- raise AutomataError("Only 1 constraint and 1 reset are supported")
- envs += self.__extract_env_var(constr)
- events.append(ev)
- else:
- # state labels have the format:
- # "enable_fired" [label = "enable_fired\ncondition"];
- # ----- label is here -----^^^^^
- # label and node name must be the same, condition is optional
- state = line.split("label")[1].split('"')[1]
- _, *constr = state.split("\\n")
- if constr:
- if len(constr) > 1:
- raise AutomataError("Only 1 constraint is supported in the state")
- envs += self.__extract_env_var([constr[0].replace(" ", "")])
+
+ for transition in self.transitions:
+ events.append(transition.event)
+
+ if transition.reset:
+ envs.append(transition.reset.env)
+ self.env_stored.add(transition.reset.env)
+ if transition.rule:
+ for c, _ in transition.rule.rules:
+ envs.append(c.env)
+ self.__extract_env_var(c)
+
+ for state in self.states:
+ if state.inv:
+ envs.append(state.inv.env)
+ self.__extract_env_var(state.inv)
return sorted(set(events)), sorted(set(envs))
- def _split_constraint_expr(self, constr: list[str]) -> Iterator[tuple[str,
- str | None]]:
- """
- Get a list of strings of the type constr1 && constr2 and returns a list of
- constraints and separators: [[constr1,"&&"],[constr2,None]]
- """
- exprs = []
- seps = []
- for c in constr:
- while "&&" in c or "||" in c:
- a = c.find("&&")
- o = c.find("||")
- pos = a if o < 0 or 0 < a < o else o
- exprs.append(c[:pos].replace(" ", ""))
- seps.append(c[pos:pos + 2].replace(" ", ""))
- c = c[pos + 2:].replace(" ", "")
- exprs.append(c)
- seps.append(None)
- return zip(exprs, seps)
-
- def __extract_env_var(self, constraint: list[str]) -> list[str]:
- env = []
- for c, _ in self._split_constraint_expr(constraint):
- rule = self.constraint_rule.search(c)
- reset = self.constraint_reset.search(c)
- if rule:
- env.append(rule["env"])
- if rule.groupdict().get("unit"):
- self.env_types[rule["env"]] = rule["unit"]
- if rule["val"][0].isalpha():
- self.constraint_vars.add(rule["val"])
- # try to infer unit from constants or parameters
- val_for_unit = rule["val"].lower().replace("()", "")
- if val_for_unit.endswith("_ns"):
- self.env_types[rule["env"]] = "ns"
- if val_for_unit.endswith("_jiffies"):
- self.env_types[rule["env"]] = "j"
- if reset:
- env.append(reset["env"])
- # environment variables that are reset need a storage
- self.env_stored.add(reset["env"])
- return env
-
- def __create_matrix(self) -> tuple[list[list[str]], dict[_ConstraintKey, list[str]]]:
+ def __extract_env_var(self, constraint: ConstraintCondition):
+ if constraint.unit:
+ self.env_types[constraint.env] = constraint.unit
+ if constraint.val[0].isalpha():
+ self.constraint_vars.add(constraint.val)
+
+ def __create_matrix(self) -> list[list[str]]:
# transform the array into a dictionary
events = self.events
- states = self.states
+ states = [s.name for s in self.states]
events_dict = {}
states_dict = {}
nr_event = 0
@@ -292,39 +503,16 @@ class Automata:
# declare the matrix....
matrix = [[self.invalid_state_str for _ in range(nr_event)] for _ in range(nr_state)]
- constraints: dict[_ConstraintKey, list[str]] = {}
-
- # and we are back! Let's fill the matrix
- cursor = self.__get_cursor_begin_events()
-
- for line in map(str.lstrip,
- islice(self.__dot_lines, cursor, None)):
-
- if not line or line[0] != '"':
- break
-
- split_line = line.split()
-
- if len(split_line) > 2 and split_line[1] == "->":
- origin_state = split_line[0].replace('"', '').replace(',', '_')
- dest_state = split_line[2].replace('"', '').replace(',', '_')
- possible_events = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '')
- for event in possible_events.split("\\n"):
- event, *constr = event.split(";")
- if constr:
- key = _EventConstraintKey(states_dict[origin_state], events_dict[event])
- constraints[key] = constr
- # those events reset also on self loops
- if origin_state == dest_state and "reset" in "".join(constr):
- self.self_loop_reset_events.add(event)
- matrix[states_dict[origin_state]][events_dict[event]] = dest_state
- else:
- state = line.split("label")[1].split('"')[1]
- state, *constr = state.replace(" ", "").split("\\n")
- if constr:
- constraints[_StateConstraintKey(states_dict[state])] = constr
- return matrix, constraints
+ for transition in self.transitions:
+ src, dst = transition.src, transition.dst
+ event = transition.event
+ if src == dst and transition.reset:
+ # those events reset also on self loops
+ self.self_loop_reset_events.add(event)
+ matrix[states_dict[src]][events_dict[event]] = dst
+
+ return matrix
def __store_init_events(self) -> tuple[list[bool], list[bool]]:
events_start = [False] * len(self.events)
@@ -336,7 +524,7 @@ class Automata:
for j in range(len(self.states)):
if self.function[j][i] != self.invalid_state_str:
curr_event_used += 1
- if self.function[j][i] == self.initial_state:
+ if self.function[j][i] == self.initial_state.name:
curr_event_will_init += 1
if self.function[0][i] != self.invalid_state_str:
curr_event_from_init = True
@@ -359,10 +547,3 @@ class Automata:
def is_hybrid_automata(self) -> bool:
return bool(self.envs)
-
- def is_event_constraint(self, key: _ConstraintKey) -> bool:
- """
- Given the key in self.constraints return true if it is an event
- constraint, false if it is a state constraint
- """
- return isinstance(key, _EventConstraintKey)
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index fc85ba1f649e..22938ce1bf6c 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -29,10 +29,10 @@ class Dot2c(Automata):
def __get_enum_states_content(self) -> list[str]:
buff = []
- buff.append(f"\t{self.initial_state}{self.enum_suffix},")
+ buff.append(f"\t{self.initial_state.name}{self.enum_suffix},")
for state in self.states:
if state != self.initial_state:
- buff.append(f"\t{state}{self.enum_suffix},")
+ buff.append(f"\t{state.name}{self.enum_suffix},")
buff.append(f"\tstate_max{self.enum_suffix},")
return buff
@@ -142,7 +142,7 @@ class Dot2c(Automata):
def format_aut_init_states_string(self) -> list[str]:
buff = []
buff.append("\t.state_names = {")
- buff.append(self.__get_string_vector_per_line_content(self.states))
+ buff.append(self.__get_string_vector_per_line_content([s.name for s in self.states]))
buff.append("\t},")
return buff
@@ -159,7 +159,7 @@ class Dot2c(Automata):
return buff
def __get_max_strlen_of_states(self) -> int:
- max_state_name = len(max(self.states, key=len))
+ max_state_name = max((len(s.name) for s in self.states))
return max(max_state_name, len(self.invalid_state_str))
def get_aut_init_function(self) -> str:
@@ -199,7 +199,7 @@ class Dot2c(Automata):
return buff
def get_aut_init_initial_state(self) -> str:
- return self.initial_state
+ return self.initial_state.name
def format_aut_init_initial_state(self) -> list[str]:
buff = []
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index 110cfd69e53a..fd3254ea5b4d 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -6,17 +6,18 @@
# dot2k: transform dot files into a monitor for the Linux kernel.
#
# For further information, see:
-# Documentation/trace/rv/da_monitor_synthesis.rst
+# Documentation/trace/rv/monitor_synthesis.rst
-from collections import deque
from .dot2c import Dot2c
from .generator import Monitor
-from .automata import _EventConstraintKey, _StateConstraintKey, AutomataError
-
+from .automata import ConstraintCondition, AutomataError
class dot2k(Monitor, Dot2c):
template_dir = "dot2k"
+ # only needed for the per-obj cleanup hook
+ cleanup_marker = "obj_cleanup"
+
def __init__(self, file_path, MonitorType, extra_params={}):
self.monitor_type = MonitorType
Monitor.__init__(self, extra_params)
@@ -56,18 +57,30 @@ class dot2k(Monitor, Dot2c):
buff.append(f"\tda_{handle}({event}{self.enum_suffix});")
buff.append("}")
buff.append("")
+ if self.monitor_type == "per_obj":
+ buff.append("/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */")
+ buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)")
+ buff.append("{")
+ buff.append("\tint id = /* XXX: how do I get the id? */;")
+ buff.append("\tda_destroy_storage(id);")
+ buff.append("}")
+ buff.append("")
return '\n'.join(buff)
def fill_tracepoint_attach_probe(self) -> str:
buff = []
for event in self.events:
buff.append(f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_{event});")
+ if self.monitor_type == "per_obj":
+ buff.append(f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: cleanup tracepoint */, handle_{self.cleanup_marker});")
return '\n'.join(buff)
def fill_tracepoint_detach_helper(self) -> str:
buff = []
for event in self.events:
buff.append(f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_{event});")
+ if self.monitor_type == "per_obj":
+ buff.append(f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: cleanup tracepoint */, handle_{self.cleanup_marker});")
return '\n'.join(buff)
def fill_model_h_header(self) -> list[str]:
@@ -176,7 +189,14 @@ class ha2k(dot2k):
if not self.is_hybrid_automata():
raise AutomataError("Detected deterministic automaton, use the 'da' class")
self.trace_h = self._read_template_file("trace_hybrid.h")
- self.__parse_constraints()
+ self.has_invariant = False
+ self.has_guard = False
+ for state in self.states:
+ if state.inv:
+ self.has_invariant = True
+ for transition in self.transitions:
+ if transition.rule or transition.reset:
+ self.has_guard = True
def fill_monitor_class_type(self) -> str:
if self._is_id_monitor():
@@ -209,38 +229,52 @@ class ha2k(dot2k):
value *= 10**9
return str(value) + "ull"
- def __parse_single_constraint(self, rule: dict, value: str) -> str:
- return f"ha_get_env(ha_mon, {rule["env"]}{self.enum_suffix}, time_ns) {rule["op"]} {value}"
-
- def __get_constraint_env(self, constr: str) -> str:
- """Extract the second argument from an ha_ function"""
- env = constr.split("(")[1].split()[1].rstrip(")").rstrip(",")
- assert env.removesuffix(f"_{self.name}") in self.envs
- return env
+ def __parse_guard_rule(self, rule) -> list[str]:
+ buff = []
+ for c, sep in rule.rules:
+ env = c.env + self.enum_suffix
+ op = c.op
+ val = self.__adjust_value(c.val, c.unit)
+
+ cond = f"ha_get_env(ha_mon, {env}, time_ns) {op} {val}"
+ if sep:
+ cond += f" {sep}"
+ buff.append(cond)
+ return buff
- def __start_to_invariant_check(self, constr: str) -> str:
+ def __start_to_invariant_check(self, inv: ConstraintCondition) -> str:
# by default assume the timer has ns expiration
- env = self.__get_constraint_env(constr)
clock_type = "ns"
- if self.env_types.get(env.removesuffix(f"_{self.name}")) == "j":
+ if inv.unit == "j":
clock_type = "jiffy"
- return f"return ha_check_invariant_{clock_type}(ha_mon, {env}, time_ns)"
+ value = self.__adjust_value(inv.val, inv.unit)
- def __start_to_conv(self, constr: str) -> str:
- """
- Undo the storage conversion done by ha_start_timer_
- """
- return "ha_inv_to_guard" + constr[constr.find("("):]
+ return f"return ha_check_invariant_{clock_type}(ha_mon, {inv.env}_{self.name}, time_ns, {value})"
- def __parse_timer_constraint(self, rule: dict, value: str) -> str:
+ def __parse_invariant(self, inv):
# by default assume the timer has ns expiration
clock_type = "ns"
- if self.env_types.get(rule["env"]) == "j":
+ if inv.unit == "j":
clock_type = "jiffy"
- return (f"ha_start_timer_{clock_type}(ha_mon, {rule["env"]}{self.enum_suffix},"
- f" {value}, time_ns)")
+ env = inv.env + self.enum_suffix
+ try:
+ val = int(inv.val)
+ except ValueError:
+ # it's a constant, a parameter or a function
+ val = inv.val.replace("()", "(ha_mon)")
+
+ match inv.unit:
+ case "us":
+ val *= 10**3
+ case "ms":
+ val *= 10**6
+ case "s":
+ val *= 10**9
+
+ return (f"ha_start_timer_{clock_type}(ha_mon, {env},"
+ f" {val}, time_ns)")
def __format_guard_rules(self, rules: list[str]) -> list[str]:
"""
@@ -259,124 +293,35 @@ class ha2k(dot2k):
rules = invalid_checks + rules
separator = "\n\t\t " if sum(len(r) for r in rules) > 80 else " "
- return ["res = " + separator.join(rules)]
-
- def __validate_constraint(self, key: tuple[int, int] | int, constr: str,
- rule, reset) -> None:
- # event constrains are tuples and allow both rules and reset
- # state constraints are only used for expirations (e.g. clk<N)
- if self.is_event_constraint(key):
- if not rule and not reset:
- raise AutomataError("Unrecognised event constraint "
- f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})")
- if rule and (rule["env"] in self.env_types and
- rule["env"] not in self.env_stored):
- raise AutomataError("Clocks in hybrid automata always require a storage"
- f" ({rule["env"]})")
- else:
- if not rule:
- raise AutomataError("Unrecognised state constraint "
- f"({self.states[key]}: {constr})")
- if rule["env"] not in self.env_stored:
- raise AutomataError("State constraints always require a storage "
- f"({rule["env"]})")
- if rule["op"] not in ["<", "<="]:
- raise AutomataError("State constraints must be clock expirations like"
- f" clk<N ({rule.string})")
-
- def __parse_constraints(self) -> None:
- self.guards: dict[_EventConstraintKey, str] = {}
- self.invariants: dict[_StateConstraintKey, str] = {}
- for key, constraint in self.constraints.items():
- rules = []
- resets = []
- for c, sep in self._split_constraint_expr(constraint):
- rule = self.constraint_rule.search(c)
- reset = self.constraint_reset.search(c)
- self.__validate_constraint(key, c, rule, reset)
- if rule:
- value = rule["val"]
- value_len = len(rule["val"])
- unit = None
- if rule.groupdict().get("unit"):
- value_len += len(rule["unit"])
- unit = rule["unit"]
- c = c[:-(value_len)]
- value = self.__adjust_value(value, unit)
- if self.is_event_constraint(key):
- c = self.__parse_single_constraint(rule, value)
- if sep:
- c += f" {sep}"
- else:
- c = self.__parse_timer_constraint(rule, value)
- rules.append(c)
- if reset:
- c = f"ha_reset_env(ha_mon, {reset["env"]}{self.enum_suffix}, time_ns)"
- resets.append(c)
- if self.is_event_constraint(key):
- res = self.__format_guard_rules(rules) + resets
- self.guards[key] = ";".join(res)
- else:
- self.invariants[key] = rules[0]
+ return ["res = " + separator.join(rules) + ";"]
def __fill_verify_invariants_func(self) -> list[str]:
- buff = []
- if not self.invariants:
+ if not self.has_invariant:
return []
- buff.append(
+ buff = [
f"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
\t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
\t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns)
-{{""")
-
- _else = ""
- for state, constr in sorted(self.invariants.items()):
- check_str = self.__start_to_invariant_check(constr)
- buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})")
- buff.append(f"\t\t{check_str};")
- _else = "else "
-
- buff.append("\treturn true;\n}\n")
- return buff
-
- def __fill_convert_inv_guard_func(self) -> list[str]:
- buff = []
- if not self.invariants:
- return []
-
- conflict_guards, conflict_invs = self.__find_inv_conflicts()
- if not conflict_guards and not conflict_invs:
- return []
-
- buff.append(
-f"""static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
-\t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
-\t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns)
-{{""")
- buff.append("\tif (curr_state == next_state)\n\t\treturn;")
+{{"""]
_else = ""
- for state, constr in sorted(self.invariants.items()):
- # a state with invariant can reach us without reset
- # multiple conflicts must have the same invariant, otherwise we cannot
- # know how to reset the value
- conf_i = [start for start, end in conflict_invs if end == state]
- # we can reach a guard without reset
- conf_g = [e for s, e in conflict_guards if s == state]
- if not conf_i and not conf_g:
+ for state in self.states:
+ if not state.inv:
continue
- buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})")
- buff.append(f"\t\t{self.__start_to_conv(constr)};")
+ check_str = self.__start_to_invariant_check(state.inv)
+ buff.append(f"\t{_else}if (curr_state == {state.name}{self.enum_suffix})")
+ buff.append(f"\t\t{check_str};")
_else = "else "
- buff.append("}\n")
+ buff.append("\treturn true;\n}\n")
return buff
def __fill_verify_guards_func(self) -> list[str]:
buff = []
- if not self.guards:
+
+ if not self.has_guard:
return []
buff.append(
@@ -388,14 +333,22 @@ f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
""")
_else = ""
- for edge, constr in sorted(self.guards.items()):
+ for transition in self.transitions:
+ if not transition.rule and not transition.reset:
+ continue
+
buff.append(f"\t{_else}if (curr_state == "
- f"{self.states[edge[0]]}{self.enum_suffix} && "
- f"event == {self.events[edge[1]]}{self.enum_suffix})")
- if constr.count(";") > 0:
+ f"{transition.src}{self.enum_suffix} && "
+ f"event == {transition.event}{self.enum_suffix})")
+ rule = transition.rule
+ reset = transition.reset
+ if rule and reset:
buff[-1] += " {"
- buff += [f"\t\t{c};" for c in constr.split(";")]
- if constr.count(";") > 0:
+ if rule:
+ buff.append("\t\t" + self.__format_guard_rules(self.__parse_guard_rule(rule))[0])
+ if reset:
+ buff.append(f"\t\tha_reset_env(ha_mon, {reset.env}{self.enum_suffix}, time_ns);")
+ if rule and reset:
_else = "} else "
else:
_else = "else "
@@ -404,64 +357,15 @@ f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
buff.append("\treturn res;\n}\n")
return buff
- def __find_inv_conflicts(self) -> tuple[set[tuple[int, _EventConstraintKey]],
- set[tuple[int, _StateConstraintKey]]]:
- """
- Run a breadth first search from all states with an invariant.
- Find any conflicting constraints reachable from there, this can be
- another state with an invariant or an edge with a non-reset guard.
- Stop when we find a reset.
-
- Return the set of conflicting guards and invariants as tuples of
- conflicting state and constraint key.
- """
- conflict_guards: set[tuple[int, _EventConstraintKey]] = set()
- conflict_invs: set[tuple[int, _StateConstraintKey]] = set()
- for start_idx in self.invariants:
- queue = deque([(start_idx, 0)]) # (state_idx, distance)
- env = self.__get_constraint_env(self.invariants[start_idx])
-
- while queue:
- curr_idx, distance = queue.popleft()
-
- # Check state condition
- if curr_idx != start_idx and curr_idx in self.invariants:
- conflict_invs.add((start_idx, _StateConstraintKey(curr_idx)))
- continue
-
- # Check if we should stop
- if distance > len(self.states):
- break
- if curr_idx != start_idx and distance > 1:
- continue
-
- for event_idx, next_state_name in enumerate(self.function[curr_idx]):
- if next_state_name == self.invalid_state_str:
- continue
- curr_guard = self.guards.get((curr_idx, event_idx), "")
- if "reset" in curr_guard and env in curr_guard:
- continue
-
- if env in curr_guard:
- conflict_guards.add((start_idx,
- _EventConstraintKey(curr_idx, event_idx)))
- continue
-
- next_idx = self.states.index(next_state_name)
- queue.append((next_idx, distance + 1))
-
- return conflict_guards, conflict_invs
-
def __fill_setup_invariants_func(self) -> list[str]:
- buff = []
- if not self.invariants:
+ if not self.has_invariant:
return []
- buff.append(
+ buff = [
f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
\t\t\t\t enum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
\t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns)
-{{""")
+{{"""]
conditions = ["next_state == curr_state"]
conditions += [f"event != {e}{self.enum_suffix}"
@@ -470,13 +374,20 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
buff.append(f"\tif ({condition_str})\n\t\treturn;")
_else = ""
- for state, constr in sorted(self.invariants.items()):
- buff.append(f"\t{_else}if (next_state == {self.states[state]}{self.enum_suffix})")
- buff.append(f"\t\t{constr};")
+ for state in self.states:
+ inv = state.inv
+ if not inv:
+ continue
+ inv = self.__parse_invariant(inv)
+ buff.append(f"\t{_else}if (next_state == {state.name}{self.enum_suffix})")
+ buff.append(f"\t\t{inv};")
_else = "else "
- for state in self.invariants:
- buff.append(f"\telse if (curr_state == {self.states[state]}{self.enum_suffix})")
+ for state in self.states:
+ inv = state.inv
+ if not inv:
+ continue
+ buff.append(f"\telse if (curr_state == {state.name}{self.enum_suffix})")
buff.append("\t\tha_cancel_timer(ha_mon);")
buff.append("}\n")
@@ -484,7 +395,7 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
def __fill_constr_func(self) -> list[str]:
buff = []
- if not self.constraints:
+ if not self.has_invariant and not self.has_guard:
return []
buff.append(
@@ -496,16 +407,9 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
* the next state has a constraint, cancel it in any other case and to check
* that it didn't expire before the callback run. Transitions to the same state
* without a reset never affect timers.
- * Due to the different representations between invariants and guards, there is
- * a function to convert it in case invariants or guards are reachable from
- * another invariant without reset. Those are not present if not required in
- * the model. This is all automatic but is worth checking because it may show
- * errors in the model (e.g. missing resets).
*/""")
buff += self.__fill_verify_invariants_func()
- inv_conflicts = self.__fill_convert_inv_guard_func()
- buff += inv_conflicts
buff += self.__fill_verify_guards_func()
buff += self.__fill_setup_invariants_func()
@@ -515,18 +419,15 @@ f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon,
\t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns)
{{""")
- if self.invariants:
+ if self.has_invariant:
buff.append("\tif (!ha_verify_invariants(ha_mon, curr_state, "
"event, next_state, time_ns))\n\t\treturn false;\n")
- if inv_conflicts:
- buff.append("\tha_convert_inv_guard(ha_mon, curr_state, event, "
- "next_state, time_ns);\n")
- if self.guards:
+ if self.has_guard:
buff.append("\tif (!ha_verify_guards(ha_mon, curr_state, event, "
"next_state, time_ns))\n\t\treturn false;\n")
- if self.invariants:
+ if self.has_invariant:
buff.append("\tha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);\n")
buff.append("\treturn true;\n}\n")
@@ -603,7 +504,7 @@ f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon,
return self.__fill_hybrid_get_reset_functions() + self.__fill_constr_func()
def _fill_timer_type(self) -> list:
- if self.invariants:
+ if self.has_invariant:
return [
"/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */",
"#define HA_TIMER_TYPE HA_TIMER_HRTIMER"
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 56f3bd8db850..45e2bab26cb5 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -6,7 +6,7 @@
# Abstract class for generating kernel runtime verification monitors from specification file
import platform
-import os
+from pathlib import Path
class RVGenerator:
@@ -16,36 +16,38 @@ class RVGenerator:
self.name = extra_params.get("model_name")
self.parent = extra_params.get("parent")
self.abs_template_dir = \
- os.path.join(os.path.dirname(__file__), "templates", self.template_dir)
+ Path(__file__).resolve().parent / "templates" / self.template_dir
self.main_c = self._read_template_file("main.c")
self.kconfig = self._read_template_file("Kconfig")
self.description = extra_params.get("description", self.name) or "auto-generated"
self.auto_patch = extra_params.get("auto_patch")
if self.auto_patch:
- self.__fill_rv_kernel_dir()
+ self._fill_rv_kernel_dir()
- def __fill_rv_kernel_dir(self):
+ def _fill_rv_kernel_dir(self):
+ # find the kernel tree root relative to this file's location
+ resolved_path = Path(__file__).resolve()
+ if len(resolved_path.parents) > 4:
+ kernel_root = resolved_path.parents[4]
+ kernel_path = kernel_root / self.rv_dir
- # first try if we are running in the kernel tree root
- if os.path.exists(self.rv_dir):
- return
-
- # offset if we are running inside the kernel tree from verification/dot2
- kernel_path = os.path.join("../..", self.rv_dir)
+ if kernel_path.exists():
+ self.rv_dir = str(kernel_path)
+ return
- if os.path.exists(kernel_path):
- self.rv_dir = kernel_path
+ # best effort if rvgen is installed and we are at the root of a kernel tree
+ if Path(self.rv_dir).exists():
return
if platform.system() != "Linux":
raise OSError("I can only run on Linux.")
- kernel_path = os.path.join(f"/lib/modules/{platform.release()}/build", self.rv_dir)
+ kernel_path = Path(f"/lib/modules/{platform.release()}/build") / self.rv_dir
# if the current kernel is from a distro this may not be a full kernel tree
# verify that one of the files we are going to modify is available
- if os.path.exists(os.path.join(kernel_path, "rv_trace.h")):
- self.rv_dir = kernel_path
+ if (kernel_path / "rv_trace.h").exists():
+ self.rv_dir = str(kernel_path)
return
raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
@@ -57,12 +59,12 @@ class RVGenerator:
def _read_template_file(self, file):
try:
- path = os.path.join(self.abs_template_dir, file)
+ path = self.abs_template_dir / file
return self._read_file(path)
except OSError:
# Specific template file not found. Try the generic template file in the template/
# directory, which is one level up
- path = os.path.join(self.abs_template_dir, "..", file)
+ path = self.abs_template_dir.parent / file
return self._read_file(path)
def fill_parent(self):
@@ -133,7 +135,7 @@ class RVGenerator:
def _patch_file(self, file, marker, line):
assert self.auto_patch
- file_to_patch = os.path.join(self.rv_dir, file)
+ file_to_patch = Path(self.rv_dir) / file
content = self._read_file(file_to_patch)
content = content.replace(marker, line + "\n" + marker)
self.__write_file(file_to_patch, content)
@@ -187,22 +189,19 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
return f" - Move {self.name}/ to the kernel's monitor directory ({self.rv_dir}/monitors)"
def __create_directory(self):
- path = self.name
+ path = Path(self.name)
if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
- try:
- os.mkdir(path)
- except FileExistsError:
- return
+ path = Path(self.rv_dir) / "monitors" / path
+ path.mkdir(exist_ok=True)
def __write_file(self, file_name, content):
with open(file_name, 'w') as file:
file.write(content)
def _create_file(self, file_name, content):
- path = f"{self.name}/{file_name}"
+ path = Path(self.name) / file_name
if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
+ path = Path(self.rv_dir) / "monitors" / self.name / file_name
self.__write_file(path, content)
def print_files(self):
diff --git a/tools/verification/rvgen/rvgen/kunit.py b/tools/verification/rvgen/rvgen/kunit.py
new file mode 100644
index 000000000000..ed2082d7d3bc
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/kunit.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+#
+# Generator for runtime verification kunit files
+
+import re
+from pathlib import Path
+from . import generator
+
+
+class KUnitError(Exception):
+ """Exception raised for errors in KUnit generation and file handling."""
+
+
+class KUnit(generator.RVGenerator):
+ template_dir = ""
+
+ def __init__(self, extra_params={}):
+ super().__init__(extra_params)
+ self.local = extra_params.get("local", False)
+ self.kunit_c = self._read_template_file("kunit.c")
+ if not self.local:
+ self._fill_rv_kernel_dir()
+ try:
+ self.monitor_path = self.__find_monitor_c_file()
+ with open(self.monitor_path, 'r') as f:
+ self.content = f.read()
+ except OSError as e:
+ raise KUnitError(e) from e
+ self.monitor_class = self.__detect_monitor_class()
+
+ def _read_template_file(self, file):
+ if file in ("main.c", "Kconfig"):
+ return ""
+ return super()._read_template_file(file)
+
+ def __find_monitor_c_file(self) -> str:
+ """Look for the monitor file in the kernel tree or in the current folder."""
+ if not self.local:
+ path = Path(self.rv_dir) / "monitors" / self.name / f"{self.name}.c"
+ if path.exists():
+ return str(path)
+
+ path = Path(self.name) / f"{self.name}.c"
+ if path.exists():
+ return str(path)
+
+ raise FileNotFoundError(f"Could not find monitor C file for '{self.name}'")
+
+ def __extract_function_args(self, handler_name: str) -> str:
+ pattern = re.compile(
+ r'^\s*(.*?)\b' + re.escape(handler_name) + r'\(([^)]*)\)',
+ re.MULTILINE | re.DOTALL
+ )
+ match = pattern.search(self.content)
+ if not match:
+ return "/* XXX: fill handlers argument. */"
+
+ return match.group(2).strip()
+
+ def __parse_attach_handlers(self) -> list[str]:
+ """Find handlers by parsing when they are attached to tracepoints."""
+ probe_pattern = re.compile(
+ r'rv_attach_trace_probe\(.*, ([a-zA-Z0-9_]+)\)'
+ )
+ handlers = []
+ for match in probe_pattern.finditer(self.content):
+ handler = match.group(1)
+ if handler not in handlers:
+ handlers.append(handler)
+ return handlers
+
+ def __detect_monitor_class(self) -> str:
+ for c in ("da", "ha", "ltl"):
+ if f"{c}_monitor.h" in self.content:
+ return c
+ return "da"
+
+ def __fill_kunit_c(self, struct_name: str) -> str:
+ kunit_c = self.kunit_c
+ kunit_c = kunit_c.replace("%%MODEL_NAME%%", self.name)
+ kunit_c = kunit_c.replace("%%MODEL_NAME_UP%%", self.name.upper())
+ kunit_c = kunit_c.replace("%%MONITOR_CLASS%%", self.monitor_class)
+ kunit_c = kunit_c.replace("%%STRUCT_NAME%%", struct_name)
+ return kunit_c
+
+ def __fill_kunit_h(self, struct_name, prototypes) -> str:
+ return f"""/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __{self.name.upper()}_KUNIT_H
+#define __{self.name.upper()}_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct {struct_name} {{
+\tstruct rv_kunit_mon mon;
+\t{"\n\t".join(prototypes)}
+}} {struct_name};
+#endif
+
+#endif /* __{self.name.upper()}_KUNIT_H */
+"""
+
+ def __fill_monitor_handlers(self, struct_name, assignments):
+ struct_definition = f"""#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "{self.name}_kunit.h"
+
+const struct {struct_name} {struct_name} = {{
+\t.mon = RV_MON_OPS_INIT(),
+\t{"\n\t".join(assignments)}
+}};
+EXPORT_SYMBOL_IF_KUNIT({struct_name});
+#endif"""
+
+ if self.auto_patch:
+ try:
+ with open(self.monitor_path, 'w') as f:
+ f.write(f"{self.content}\n{struct_definition}\n")
+ except OSError as e:
+ raise KUnitError(f"Error patching monitor file {self.monitor_path}: {e}") from e
+ else:
+ print(f"Append the following to {self.name}.c:\n")
+ print(struct_definition)
+ print("Now complete the test and add it to rv_monitors_test.c")
+
+ def print_files(self):
+
+ handlers = self.__parse_attach_handlers()
+
+ if not handlers:
+ raise KUnitError(f"No handlers found in {self.monitor_path}")
+
+ prototypes = []
+ assignments = []
+ for handler in handlers:
+ arguments = self.__extract_function_args(handler)
+
+ prototypes.append(f"void (*{handler})({arguments});")
+ assignments.append(f".{handler} = {handler},")
+
+ struct_name = f"rv_{self.name}_ops"
+
+ self.__fill_monitor_handlers(struct_name, assignments)
+
+ dir_path = Path(self.monitor_path).parent
+
+ header_file_path = dir_path / f"{self.name}_kunit.h"
+ kunit_c_file_path = dir_path / f"{self.name}_kunit.c"
+
+ use_backup = True
+ if header_file_path.exists() or kunit_c_file_path.exists():
+ try:
+ response = input("KUnit file(s) already exist. Backup? [Y/n] ")
+ if response.strip().lower() in ("n", "no"):
+ use_backup = False
+ except EOFError:
+ print("Non-interactive session detected, backing up existing files.")
+ else:
+ use_backup = False
+
+ if use_backup:
+ for path in (header_file_path, kunit_c_file_path):
+ if path.exists():
+ try:
+ path.rename(path.with_suffix(path.suffix + ".bak"))
+ except OSError as e:
+ raise KUnitError(f"Error backing up file {path}: {e}") from e
+
+ header_content = self.__fill_kunit_h(struct_name, prototypes)
+ try:
+ with open(header_file_path, 'w') as f:
+ f.write(header_content)
+ print(f"Successfully created KUnit header file: {header_file_path}")
+ except OSError as e:
+ raise KUnitError(f"Error writing to file {header_file_path}: {e}") from e
+
+ kunit_c_content = self.__fill_kunit_c(struct_name)
+ try:
+ with open(kunit_c_file_path, 'w') as f:
+ f.write(kunit_c_content)
+ print(f"Successfully created KUnit C file: {kunit_c_file_path}")
+ except OSError as e:
+ raise KUnitError(f"Error writing to file {kunit_c_file_path}: {e}") from e
diff --git a/tools/verification/rvgen/rvgen/ltl2ba.py b/tools/verification/rvgen/rvgen/ltl2ba.py
index 016e7cf93bbb..7cebda61bce8 100644
--- a/tools/verification/rvgen/rvgen/ltl2ba.py
+++ b/tools/verification/rvgen/rvgen/ltl2ba.py
@@ -7,9 +7,7 @@
# https://doi.org/10.1007/978-0-387-34892-6_1
# With extra optimizations
-from ply.lex import lex
-from ply.yacc import yacc
-from .automata import AutomataError
+import lark
# Grammar:
# ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
@@ -30,42 +28,41 @@ from .automata import AutomataError
# imply
# equivalent
-tokens = (
- 'AND',
- 'OR',
- 'IMPLY',
- 'UNTIL',
- 'ALWAYS',
- 'EVENTUALLY',
- 'NEXT',
- 'VARIABLE',
- 'LITERAL',
- 'NOT',
- 'LPAREN',
- 'RPAREN',
- 'ASSIGN',
-)
-
-t_AND = r'and'
-t_OR = r'or'
-t_IMPLY = r'imply'
-t_UNTIL = r'until'
-t_ALWAYS = r'always'
-t_NEXT = r'next'
-t_EVENTUALLY = r'eventually'
-t_VARIABLE = r'[A-Z_0-9]+'
-t_LITERAL = r'true|false'
-t_NOT = r'not'
-t_LPAREN = r'\('
-t_RPAREN = r'\)'
-t_ASSIGN = r'='
-t_ignore_COMMENT = r'\#.*'
-t_ignore = ' \t\n'
-
-def t_error(t):
- raise AutomataError(f"Illegal character '{t.value[0]}'")
-
-lexer = lex()
+GRAMMAR = r'''
+start: assign+
+
+assign: VARIABLE "=" _ltl
+
+_ltl: _opd | binop | unop
+
+_opd : VARIABLE
+ | LITERAL
+ | "(" _ltl ")"
+
+unop: UNOP _ltl
+UNOP: "always"
+ | "eventually"
+ | "next"
+ | "not"
+
+binop: _opd BINOP _ltl
+BINOP: "until"
+ | "and"
+ | "or"
+ | "imply"
+
+VARIABLE: /[A-Z_][A-Z0-9_]*/
+LITERAL: "true" | "false"
+
+COMMENT: "#" /.*/ "\n"
+%ignore COMMENT
+
+%import common.WS
+%ignore WS
+'''
+
+class LTLError(Exception):
+ "Exception raised for malformed linear temporal logic"
class GraphNode:
uid = 0
@@ -97,7 +94,7 @@ class GraphNode:
return self.id < other.id
class ASTNode:
- uid = 1
+ uid = 0
def __init__(self, op):
self.op = op
@@ -433,90 +430,49 @@ class Literal:
node.old |= {n}
return node.expand(node_set)
-def p_spec(p):
- '''
- spec : assign
- | assign spec
- '''
- if len(p) == 3:
- p[2].append(p[1])
- p[0] = p[2]
- else:
- p[0] = [p[1]]
-
-def p_assign(p):
- '''
- assign : VARIABLE ASSIGN ltl
- '''
- p[0] = (p[1], p[3])
-
-def p_ltl(p):
- '''
- ltl : opd
- | binop
- | unop
- '''
- p[0] = p[1]
-
-def p_opd(p):
- '''
- opd : VARIABLE
- | LITERAL
- | LPAREN ltl RPAREN
- '''
- if p[1] == "true":
- p[0] = ASTNode(Literal(True))
- elif p[1] == "false":
- p[0] = ASTNode(Literal(False))
- elif p[1] == '(':
- p[0] = p[2]
- else:
- p[0] = ASTNode(Variable(p[1]))
-
-def p_unop(p):
- '''
- unop : ALWAYS ltl
- | EVENTUALLY ltl
- | NEXT ltl
- | NOT ltl
- '''
- if p[1] == "always":
- op = AlwaysOp(p[2])
- elif p[1] == "eventually":
- op = EventuallyOp(p[2])
- elif p[1] == "next":
- op = NextOp(p[2])
- elif p[1] == "not":
- op = NotOp(p[2])
- else:
- raise AutomataError(f"Invalid unary operator {p[1]}")
-
- p[0] = ASTNode(op)
-
-def p_binop(p):
- '''
- binop : opd UNTIL ltl
- | opd AND ltl
- | opd OR ltl
- | opd IMPLY ltl
- '''
- if p[2] == "and":
- op = AndOp(p[1], p[3])
- elif p[2] == "until":
- op = UntilOp(p[1], p[3])
- elif p[2] == "or":
- op = OrOp(p[1], p[3])
- elif p[2] == "imply":
- op = ImplyOp(p[1], p[3])
- else:
- raise AutomataError(f"Invalid binary operator {p[2]}")
-
- p[0] = ASTNode(op)
-
-parser = yacc()
+class Transform(lark.visitors.Transformer):
+ def unop(self, node):
+ if node[0] == "always":
+ return ASTNode(AlwaysOp(node[1]))
+ if node[0] == "eventually":
+ return ASTNode(EventuallyOp(node[1]))
+ if node[0] == "next":
+ return ASTNode(NextOp(node[1]))
+ if node[0] == "not":
+ return ASTNode(NotOp(node[1]))
+ raise ValueError("Unknown operator %s" % node[0])
+
+ def binop(self, node):
+ if node[1] == "until":
+ return ASTNode(UntilOp(node[0], node[2]))
+ if node[1] == "and":
+ return ASTNode(AndOp(node[0], node[2]))
+ if node[1] == "or":
+ return ASTNode(OrOp(node[0], node[2]))
+ if node[1] == "imply":
+ return ASTNode(ImplyOp(node[0], node[2]))
+ raise ValueError("Unknown operator %s" % node[1])
+
+ def VARIABLE(self, args):
+ return ASTNode(Variable(args))
+
+ def LITERAL(self, args):
+ return ASTNode(Literal(args == "true"))
+
+ def start(self, node):
+ return node
+
+ def assign(self, node):
+ return node[0].op.name, node[1]
+
+parser = lark.Lark(GRAMMAR)
def parse_ltl(s: str) -> ASTNode:
- spec = parser.parse(s)
+ try:
+ spec = parser.parse(s)
+ except lark.exceptions.UnexpectedInput as e:
+ raise LTLError(str(e))
+ spec = Transform().transform(spec)
rule = None
subexpr = {}
@@ -528,7 +484,7 @@ def parse_ltl(s: str) -> ASTNode:
subexpr[assign[0]] = assign[1]
if rule is None:
- raise AutomataError("Please define your specification in the \"RULE = <LTL spec>\" format")
+ raise LTLError("Please define your specification in the \"RULE = <LTL spec>\" format")
for node in rule:
if not isinstance(node.op, Variable):
diff --git a/tools/verification/rvgen/rvgen/ltl2k.py b/tools/verification/rvgen/rvgen/ltl2k.py
index 81fd1f5ea5ea..f3781a3e0856 100644
--- a/tools/verification/rvgen/rvgen/ltl2k.py
+++ b/tools/verification/rvgen/rvgen/ltl2k.py
@@ -222,7 +222,7 @@ class ltl2k(generator.Monitor):
return f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);"
def fill_tracepoint_detach_helper(self):
- return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_sample_event);"
+ return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);"
def fill_atoms_init(self):
buff = []
diff --git a/tools/verification/rvgen/rvgen/templates/container/main.c b/tools/verification/rvgen/rvgen/templates/container/main.c
index 5fc89b46f279..e6a20d74886c 100644
--- a/tools/verification/rvgen/rvgen/templates/container/main.c
+++ b/tools/verification/rvgen/rvgen/templates/container/main.c
@@ -31,5 +31,5 @@ module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index 889446760e3c..bd3e0aab9cc5 100644
--- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -79,5 +79,5 @@ module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/rvgen/templates/kunit.c b/tools/verification/rvgen/rvgen/templates/kunit.c
new file mode 100644
index 000000000000..62092b5cc6d4
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "%%MODEL_NAME%%_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_%%MODEL_NAME_UP%%)
+
+static void rv_test_%%MODEL_NAME%%(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &%%STRUCT_NAME%%.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * %%STRUCT_NAME%%.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_%%MODEL_NAME%% rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
index f85d076fbf78..c33f21535a7a 100644
--- a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
@@ -77,7 +77,7 @@ static void disable_%%MODEL_NAME%%(void)
/*
* This is the monitor register section.
*/
-static struct rv_monitor rv_%%MODEL_NAME%% = {
+static struct rv_monitor rv_this = {
.name = "%%MODEL_NAME%%",
.description = "%%DESCRIPTION%%",
.enable = enable_%%MODEL_NAME%%,
@@ -86,17 +86,17 @@ static struct rv_monitor rv_%%MODEL_NAME%% = {
static int __init register_%%MODEL_NAME%%(void)
{
- return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%);
+ return rv_register_monitor(&rv_this, %%PARENT%%);
}
static void __exit unregister_%%MODEL_NAME%%(void)
{
- rv_unregister_monitor(&rv_%%MODEL_NAME%%);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR(/* TODO */);
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/tests/golden/da_global/Kconfig b/tools/verification/rvgen/tests/golden/da_global/Kconfig
new file mode 100644
index 000000000000..799fbf11c3ac
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_GLOBAL
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "da_global monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.c b/tools/verification/rvgen/tests/golden/da_global/da_global.c
new file mode 100644
index 000000000000..71b26ae2d51c
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_global"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_GLOBAL
+#include "da_global.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_da_global);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_da_global);
+}
+
+static int enable_da_global(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_da_global(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_global",
+ .description = "auto-generated",
+ .enable = enable_da_global,
+ .disable = disable_da_global,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_global(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_global(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_global);
+module_exit(unregister_da_global);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_global: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.h b/tools/verification/rvgen/tests/golden/da_global/da_global.h
new file mode 100644
index 000000000000..40b1f1c0c681
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_global automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_global
+
+enum states_da_global {
+ state_a_da_global,
+ state_b_da_global,
+ state_max_da_global,
+};
+
+#define INVALID_STATE state_max_da_global
+
+enum events_da_global {
+ event_1_da_global,
+ event_2_da_global,
+ event_max_da_global,
+};
+
+struct automaton_da_global {
+ char *state_names[state_max_da_global];
+ char *event_names[event_max_da_global];
+ unsigned char function[state_max_da_global][event_max_da_global];
+ unsigned char initial_state;
+ bool final_states[state_max_da_global];
+};
+
+static const struct automaton_da_global automaton_da_global = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_da_global, state_a_da_global },
+ { INVALID_STATE, state_a_da_global },
+ },
+ .initial_state = state_a_da_global,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
new file mode 100644
index 000000000000..4d2730b71dd0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_GLOBAL
+DEFINE_EVENT(event_da_monitor, event_da_global,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_da_global,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_DA_GLOBAL */
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
new file mode 100644
index 000000000000..249ba3aee8d7
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PEROBJ_PARENT
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_PARENT_MON
+ default y
+ select DA_MON_EVENTS_ID
+ bool "da_perobj_parent monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
new file mode 100644
index 000000000000..5c5d300b4183
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_perobj_parent"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/parent_mon/parent_mon.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_OBJ
+typedef /* XXX: define the target type */ *monitor_target;
+#include "da_perobj_parent.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_start_run_event(id, t, event_1_da_perobj_parent);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_2_da_perobj_parent);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_3_da_perobj_parent);
+}
+
+/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */
+static void handle_obj_cleanup(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ da_destroy_storage(id);
+}
+
+static int enable_da_perobj_parent(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: cleanup tracepoint */, handle_obj_cleanup);
+
+ return 0;
+}
+
+static void disable_da_perobj_parent(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: cleanup tracepoint */, handle_obj_cleanup);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_perobj_parent",
+ .description = "auto-generated",
+ .enable = enable_da_perobj_parent,
+ .disable = disable_da_perobj_parent,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_perobj_parent(void)
+{
+ return rv_register_monitor(&rv_this, &rv_parent_mon);
+}
+
+static void __exit unregister_da_perobj_parent(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_perobj_parent);
+module_exit(unregister_da_perobj_parent);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_perobj_parent: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
new file mode 100644
index 000000000000..3c8dc3b22443
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_perobj_parent automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_perobj_parent
+
+enum states_da_perobj_parent {
+ state_a_da_perobj_parent,
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ state_max_da_perobj_parent,
+};
+
+#define INVALID_STATE state_max_da_perobj_parent
+
+enum events_da_perobj_parent {
+ event_1_da_perobj_parent,
+ event_2_da_perobj_parent,
+ event_3_da_perobj_parent,
+ event_max_da_perobj_parent,
+};
+
+struct automaton_da_perobj_parent {
+ char *state_names[state_max_da_perobj_parent];
+ char *event_names[event_max_da_perobj_parent];
+ unsigned char function[state_max_da_perobj_parent][event_max_da_perobj_parent];
+ unsigned char initial_state;
+ bool final_states[state_max_da_perobj_parent];
+};
+
+static const struct automaton_da_perobj_parent automaton_da_perobj_parent = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_perobj_parent,
+ state_c_da_perobj_parent,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_perobj_parent,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
new file mode 100644
index 000000000000..59bfca8f73d2
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PEROBJ_PARENT
+DEFINE_EVENT(event_da_monitor_id, event_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PEROBJ_PARENT */
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
new file mode 100644
index 000000000000..c6f350179098
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PERTASK_DESC
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_ID
+ bool "da_pertask_desc monitor"
+ help
+ Custom description for testing
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
new file mode 100644
index 000000000000..c1ae5078c4f9
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_pertask_desc"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+#include "da_pertask_desc.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_run_event(p, event_1_da_pertask_desc);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_2_da_pertask_desc);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_3_da_pertask_desc);
+}
+
+static int enable_da_pertask_desc(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ return 0;
+}
+
+static void disable_da_pertask_desc(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_pertask_desc",
+ .description = "Custom description for testing",
+ .enable = enable_da_pertask_desc,
+ .disable = disable_da_pertask_desc,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_pertask_desc(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_pertask_desc(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_pertask_desc);
+module_exit(unregister_da_pertask_desc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_pertask_desc: Custom description for testing");
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
new file mode 100644
index 000000000000..837b238754b0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_pertask_desc automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_pertask_desc
+
+enum states_da_pertask_desc {
+ state_a_da_pertask_desc,
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ state_max_da_pertask_desc,
+};
+
+#define INVALID_STATE state_max_da_pertask_desc
+
+enum events_da_pertask_desc {
+ event_1_da_pertask_desc,
+ event_2_da_pertask_desc,
+ event_3_da_pertask_desc,
+ event_max_da_pertask_desc,
+};
+
+struct automaton_da_pertask_desc {
+ char *state_names[state_max_da_pertask_desc];
+ char *event_names[event_max_da_pertask_desc];
+ unsigned char function[state_max_da_pertask_desc][event_max_da_pertask_desc];
+ unsigned char initial_state;
+ bool final_states[state_max_da_pertask_desc];
+};
+
+static const struct automaton_da_pertask_desc automaton_da_pertask_desc = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_pertask_desc,
+ state_c_da_pertask_desc,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_pertask_desc,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
new file mode 100644
index 000000000000..4e6086c4d86e
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PERTASK_DESC
+DEFINE_EVENT(event_da_monitor_id, event_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PERTASK_DESC */
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
new file mode 100644
index 000000000000..0cc185ccfddf
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_HA_PERCPU
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_IMPLICIT
+ bool "ha_percpu monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
new file mode 100644
index 000000000000..3a72e867122c
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ha_percpu"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "ha_percpu.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_ha_percpu)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_ha_percpu)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns, bar_ns(ha_mon));
+ else if (curr_state == S2_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns, BAR_NS(ha_mon));
+ return true;
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S0_ha_percpu && event == event1_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event2_ha_percpu) {
+ res = ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ } else if (curr_state == S2_ha_percpu && event == event1_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ ha_get_env(ha_mon, clk_ha_percpu, time_ns) < foo_ns;
+ else if (curr_state == S3_ha_percpu && event == event0_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_ha_percpu, time_ns) == 0ull);
+ else if (curr_state == S3_ha_percpu && event == event1_ha_percpu) {
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_ha_percpu)
+ return;
+ if (next_state == S0_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_ha_percpu)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_ha_percpu)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event0_ha_percpu);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event1_ha_percpu);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event2_ha_percpu);
+}
+
+static int enable_ha_percpu(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_ha_percpu(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "ha_percpu",
+ .description = "auto-generated",
+ .enable = enable_ha_percpu,
+ .disable = disable_ha_percpu,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_ha_percpu(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_ha_percpu(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_ha_percpu);
+module_exit(unregister_ha_percpu);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("ha_percpu: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
new file mode 100644
index 000000000000..2538db4f6a26
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of ha_percpu automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME ha_percpu
+
+enum states_ha_percpu {
+ S0_ha_percpu,
+ S1_ha_percpu,
+ S2_ha_percpu,
+ S3_ha_percpu,
+ state_max_ha_percpu,
+};
+
+#define INVALID_STATE state_max_ha_percpu
+
+enum events_ha_percpu {
+ event0_ha_percpu,
+ event1_ha_percpu,
+ event2_ha_percpu,
+ event_max_ha_percpu,
+};
+
+enum envs_ha_percpu {
+ clk_ha_percpu,
+ env1_ha_percpu,
+ env2_ha_percpu,
+ env_max_ha_percpu,
+ env_max_stored_ha_percpu = env1_ha_percpu,
+};
+
+_Static_assert(env_max_stored_ha_percpu <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_ha_percpu {
+ char *state_names[state_max_ha_percpu];
+ char *event_names[event_max_ha_percpu];
+ char *env_names[env_max_ha_percpu];
+ unsigned char function[state_max_ha_percpu][event_max_ha_percpu];
+ unsigned char initial_state;
+ bool final_states[state_max_ha_percpu];
+};
+
+static const struct automaton_ha_percpu automaton_ha_percpu = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ { S0_ha_percpu, INVALID_STATE, S2_ha_percpu },
+ { INVALID_STATE, S2_ha_percpu, S3_ha_percpu },
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ },
+ .initial_state = S0_ha_percpu,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
new file mode 100644
index 000000000000..074ddff6a60d
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_HA_PERCPU
+DEFINE_EVENT(event_da_monitor, event_ha_percpu,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_ha_percpu,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+
+DEFINE_EVENT(error_env_da_monitor, error_env_ha_percpu,
+ TP_PROTO(char *state, char *event, char *env),
+ TP_ARGS(state, event, env));
+#endif /* CONFIG_RV_MON_HA_PERCPU */
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
new file mode 100644
index 000000000000..b37f46670bfd
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_LTL_PERTASK
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "ltl_pertask monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
new file mode 100644
index 000000000000..2c60b5c5b4e0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ltl_pertask"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "ltl_pertask.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_ltl_pertask(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_ltl_pertask(void)
+{
+ rv_detach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "ltl_pertask",
+ .description = "auto-generated",
+ .enable = enable_ltl_pertask,
+ .disable = disable_ltl_pertask,
+};
+
+static int __init register_ltl_pertask(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_ltl_pertask(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_ltl_pertask);
+module_exit(unregister_ltl_pertask);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("ltl_pertask: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
new file mode 100644
index 000000000000..7e5de351b8fa
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME ltl_pertask
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
new file mode 100644
index 000000000000..ebd53621a5b1
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_LTL_PERTASK
+DEFINE_EVENT(event_ltl_monitor_id, event_ltl_pertask,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_ltl_pertask,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_LTL_PERTASK */
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
new file mode 100644
index 000000000000..175a416f8b18
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_BAK_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "test_bak_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
new file mode 100644
index 000000000000..16579c1c6910
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_bak_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_bak_kunit.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_bak_kunit(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_bak_kunit(void)
+{
+ rv_detach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_bak_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_bak_kunit,
+ .disable = disable_test_bak_kunit,
+};
+
+static int __init register_test_bak_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_bak_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_bak_kunit);
+module_exit(unregister_test_bak_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_bak_kunit: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
new file mode 100644
index 000000000000..2bfe4e37cea7
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_bak_kunit
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
new file mode 100644
index 000000000000..e2b9354034cc
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_bak_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_BAK_KUNIT)
+
+static void rv_test_test_bak_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_bak_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_bak_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_bak_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
new file mode 100644
index 000000000000..f747925bf542
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
@@ -0,0 +1 @@
+DUMMY
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
new file mode 100644
index 000000000000..585c4803be23
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_BAK_KUNIT_KUNIT_H
+#define __TEST_BAK_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_bak_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_example_event)(void *data, /* XXX: fill header */);
+} rv_test_bak_kunit_ops;
+#endif
+
+#endif /* __TEST_BAK_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
new file mode 100644
index 000000000000..b984208838c4
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_BAK_KUNIT
+DEFINE_EVENT(event_ltl_monitor_id, event_test_bak_kunit,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_bak_kunit,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_BAK_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_container/Kconfig b/tools/verification/rvgen/tests/golden/test_container/Kconfig
new file mode 100644
index 000000000000..2becb65dddad
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/Kconfig
@@ -0,0 +1,5 @@
+config RV_MON_TEST_CONTAINER
+ depends on RV
+ bool "test_container monitor"
+ help
+ Test container for grouping monitors
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.c b/tools/verification/rvgen/tests/golden/test_container/test_container.c
new file mode 100644
index 000000000000..e7e34592c6c5
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+
+#define MODULE_NAME "test_container"
+
+#include "test_container.h"
+
+struct rv_monitor rv_test_container = {
+ .name = "test_container",
+ .description = "Test container for grouping monitors",
+ .enable = NULL,
+ .disable = NULL,
+ .reset = NULL,
+ .enabled = 0,
+};
+
+static int __init register_test_container(void)
+{
+ return rv_register_monitor(&rv_test_container, NULL);
+}
+
+static void __exit unregister_test_container(void)
+{
+ rv_unregister_monitor(&rv_test_container);
+}
+
+module_init(register_test_container);
+module_exit(unregister_test_container);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_container: Test container for grouping monitors");
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.h b/tools/verification/rvgen/tests/golden/test_container/test_container.h
new file mode 100644
index 000000000000..83e434432650
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern struct rv_monitor rv_test_container;
diff --git a/tools/verification/rvgen/tests/golden/test_da/Kconfig b/tools/verification/rvgen/tests/golden/test_da/Kconfig
new file mode 100644
index 000000000000..0143a148ef34
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_DA
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "test_da monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.c b/tools/verification/rvgen/tests/golden/test_da/test_da.c
new file mode 100644
index 000000000000..59b8dfabbbf1
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_da"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "test_da.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_test_da);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_test_da);
+}
+
+static int enable_test_da(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_test_da(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_da",
+ .description = "auto-generated",
+ .enable = enable_test_da,
+ .disable = disable_test_da,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_da(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_da(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_da);
+module_exit(unregister_test_da);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_da: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.h b/tools/verification/rvgen/tests/golden/test_da/test_da.h
new file mode 100644
index 000000000000..d55795efbb61
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_da automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_da
+
+enum states_test_da {
+ state_a_test_da,
+ state_b_test_da,
+ state_max_test_da,
+};
+
+#define INVALID_STATE state_max_test_da
+
+enum events_test_da {
+ event_1_test_da,
+ event_2_test_da,
+ event_max_test_da,
+};
+
+struct automaton_test_da {
+ char *state_names[state_max_test_da];
+ char *event_names[event_max_test_da];
+ unsigned char function[state_max_test_da][event_max_test_da];
+ unsigned char initial_state;
+ bool final_states[state_max_test_da];
+};
+
+static const struct automaton_test_da automaton_test_da = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_test_da, state_a_test_da },
+ { INVALID_STATE, state_a_test_da },
+ },
+ .initial_state = state_a_test_da,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
new file mode 100644
index 000000000000..8bd67115d244
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_DA
+DEFINE_EVENT(event_da_monitor, event_test_da,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_test_da,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_TEST_DA */
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
new file mode 100644
index 000000000000..6d664ba5624d
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_DA_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "test_da_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
new file mode 100644
index 000000000000..effd26548b07
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_da_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "test_da_kunit.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_test_da_kunit);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_test_da_kunit);
+}
+
+static int enable_test_da_kunit(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_test_da_kunit(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_da_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_da_kunit,
+ .disable = disable_test_da_kunit,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_da_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_da_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_da_kunit);
+module_exit(unregister_test_da_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_da_kunit: auto-generated");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "test_da_kunit_kunit.h"
+
+const struct rv_test_da_kunit_ops rv_test_da_kunit_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_event_1 = handle_event_1,
+ .handle_event_2 = handle_event_2,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_test_da_kunit_ops);
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
new file mode 100644
index 000000000000..290a9454caa4
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_da_kunit automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_da_kunit
+
+enum states_test_da_kunit {
+ state_a_test_da_kunit,
+ state_b_test_da_kunit,
+ state_max_test_da_kunit,
+};
+
+#define INVALID_STATE state_max_test_da_kunit
+
+enum events_test_da_kunit {
+ event_1_test_da_kunit,
+ event_2_test_da_kunit,
+ event_max_test_da_kunit,
+};
+
+struct automaton_test_da_kunit {
+ char *state_names[state_max_test_da_kunit];
+ char *event_names[event_max_test_da_kunit];
+ unsigned char function[state_max_test_da_kunit][event_max_test_da_kunit];
+ unsigned char initial_state;
+ bool final_states[state_max_test_da_kunit];
+};
+
+static const struct automaton_test_da_kunit automaton_test_da_kunit = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_test_da_kunit, state_a_test_da_kunit },
+ { INVALID_STATE, state_a_test_da_kunit },
+ },
+ .initial_state = state_a_test_da_kunit,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
new file mode 100644
index 000000000000..17826a5c47c8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_da_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_DA_KUNIT)
+
+static void rv_test_test_da_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_da_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_da_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_da_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
new file mode 100644
index 000000000000..0094215ff4fb
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_DA_KUNIT_KUNIT_H
+#define __TEST_DA_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_da_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_event_1)(void *data, /* XXX: fill header */);
+ void (*handle_event_2)(void *data, /* XXX: fill header */);
+} rv_test_da_kunit_ops;
+#endif
+
+#endif /* __TEST_DA_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
new file mode 100644
index 000000000000..16804a79e834
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_DA_KUNIT
+DEFINE_EVENT(event_da_monitor, event_test_da_kunit,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_test_da_kunit,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_TEST_DA_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_ha/Kconfig b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
new file mode 100644
index 000000000000..f4048290c774
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_HA
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "test_ha monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.c b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
new file mode 100644
index 000000000000..9047ff725546
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ha"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "test_ha.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_test_ha)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_test_ha)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns, bar_ns(ha_mon));
+ else if (curr_state == S2_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns, BAR_NS(ha_mon));
+ return true;
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S0_test_ha && event == event1_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event2_test_ha) {
+ res = ha_get_env(ha_mon, env1_test_ha, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ } else if (curr_state == S2_test_ha && event == event1_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ ha_get_env(ha_mon, clk_test_ha, time_ns) < foo_ns;
+ else if (curr_state == S3_test_ha && event == event0_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_test_ha, time_ns) == 0ull);
+ else if (curr_state == S3_test_ha && event == event1_test_ha) {
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_test_ha, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_test_ha)
+ return;
+ if (next_state == S0_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_test_ha)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_test_ha)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_event(p, event0_test_ha);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event1_test_ha);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event2_test_ha);
+}
+
+static int enable_test_ha(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_test_ha(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ha",
+ .description = "auto-generated",
+ .enable = enable_test_ha,
+ .disable = disable_test_ha,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_ha(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ha(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ha);
+module_exit(unregister_test_ha);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ha: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
new file mode 100644
index 000000000000..949fa4453403
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_ha automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_ha
+
+enum states_test_ha {
+ S0_test_ha,
+ S1_test_ha,
+ S2_test_ha,
+ S3_test_ha,
+ state_max_test_ha,
+};
+
+#define INVALID_STATE state_max_test_ha
+
+enum events_test_ha {
+ event0_test_ha,
+ event1_test_ha,
+ event2_test_ha,
+ event_max_test_ha,
+};
+
+enum envs_test_ha {
+ clk_test_ha,
+ env1_test_ha,
+ env2_test_ha,
+ env_max_test_ha,
+ env_max_stored_test_ha = env1_test_ha,
+};
+
+_Static_assert(env_max_stored_test_ha <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_test_ha {
+ char *state_names[state_max_test_ha];
+ char *event_names[event_max_test_ha];
+ char *env_names[env_max_test_ha];
+ unsigned char function[state_max_test_ha][event_max_test_ha];
+ unsigned char initial_state;
+ bool final_states[state_max_test_ha];
+};
+
+static const struct automaton_test_ha automaton_test_ha = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ { S0_test_ha, INVALID_STATE, S2_test_ha },
+ { INVALID_STATE, S2_test_ha, S3_test_ha },
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ },
+ .initial_state = S0_test_ha,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
new file mode 100644
index 000000000000..381bafcb3322
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_HA
+DEFINE_EVENT(event_da_monitor_id, event_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_test_ha,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *env),
+ TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TEST_HA */
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
new file mode 100644
index 000000000000..6c48770ace1a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_HA_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "test_ha_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
new file mode 100644
index 000000000000..239b0539df18
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ha_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "test_ha_kunit.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns)
+{
+ if (env == clk_test_ha_kunit)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_test_ha_kunit)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_test_ha_kunit)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns)
+{
+ if (env == clk_test_ha_kunit)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_test_ha_kunit)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns, bar_ns(ha_mon));
+ else if (curr_state == S2_test_ha_kunit)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns, BAR_NS(ha_mon));
+ return true;
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_test_ha_kunit && event == event0_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S0_test_ha_kunit && event == event1_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S1_test_ha_kunit && event == event0_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S1_test_ha_kunit && event == event2_test_ha_kunit) {
+ res = ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ } else if (curr_state == S2_test_ha_kunit && event == event1_test_ha_kunit)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < foo_ns;
+ else if (curr_state == S3_test_ha_kunit && event == event0_test_ha_kunit)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_test_ha_kunit, time_ns) == 0ull);
+ else if (curr_state == S3_test_ha_kunit && event == event1_test_ha_kunit) {
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_test_ha_kunit)
+ return;
+ if (next_state == S0_test_ha_kunit)
+ ha_start_timer_ns(ha_mon, clk_test_ha_kunit, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_test_ha_kunit)
+ ha_start_timer_ns(ha_mon, clk_test_ha_kunit, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_test_ha_kunit)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_test_ha_kunit)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_event(p, event0_test_ha_kunit);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event1_test_ha_kunit);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event2_test_ha_kunit);
+}
+
+static int enable_test_ha_kunit(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_test_ha_kunit(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ha_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_ha_kunit,
+ .disable = disable_test_ha_kunit,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_ha_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ha_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ha_kunit);
+module_exit(unregister_test_ha_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ha_kunit: auto-generated");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "test_ha_kunit_kunit.h"
+
+const struct rv_test_ha_kunit_ops rv_test_ha_kunit_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_event0 = handle_event0,
+ .handle_event1 = handle_event1,
+ .handle_event2 = handle_event2,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_test_ha_kunit_ops);
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
new file mode 100644
index 000000000000..5c428f818bdf
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_ha_kunit automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_ha_kunit
+
+enum states_test_ha_kunit {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ S2_test_ha_kunit,
+ S3_test_ha_kunit,
+ state_max_test_ha_kunit,
+};
+
+#define INVALID_STATE state_max_test_ha_kunit
+
+enum events_test_ha_kunit {
+ event0_test_ha_kunit,
+ event1_test_ha_kunit,
+ event2_test_ha_kunit,
+ event_max_test_ha_kunit,
+};
+
+enum envs_test_ha_kunit {
+ clk_test_ha_kunit,
+ env1_test_ha_kunit,
+ env2_test_ha_kunit,
+ env_max_test_ha_kunit,
+ env_max_stored_test_ha_kunit = env1_test_ha_kunit,
+};
+
+_Static_assert(env_max_stored_test_ha_kunit <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_test_ha_kunit {
+ char *state_names[state_max_test_ha_kunit];
+ char *event_names[event_max_test_ha_kunit];
+ char *env_names[env_max_test_ha_kunit];
+ unsigned char function[state_max_test_ha_kunit][event_max_test_ha_kunit];
+ unsigned char initial_state;
+ bool final_states[state_max_test_ha_kunit];
+};
+
+static const struct automaton_test_ha_kunit automaton_test_ha_kunit = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ INVALID_STATE,
+ },
+ {
+ S0_test_ha_kunit,
+ INVALID_STATE,
+ S2_test_ha_kunit,
+ },
+ {
+ INVALID_STATE,
+ S2_test_ha_kunit,
+ S3_test_ha_kunit,
+ },
+ {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = S0_test_ha_kunit,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
new file mode 100644
index 000000000000..6214a4aa6d25
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_ha_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_HA_KUNIT)
+
+static void rv_test_test_ha_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_ha_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_ha_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_ha_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
new file mode 100644
index 000000000000..0b2030cb644a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_HA_KUNIT_KUNIT_H
+#define __TEST_HA_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_ha_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_event0)(void *data, /* XXX: fill header */);
+ void (*handle_event1)(void *data, /* XXX: fill header */);
+ void (*handle_event2)(void *data, /* XXX: fill header */);
+} rv_test_ha_kunit_ops;
+#endif
+
+#endif /* __TEST_HA_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
new file mode 100644
index 000000000000..6c13ee0068d3
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_HA_KUNIT
+DEFINE_EVENT(event_da_monitor_id, event_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event, char *env),
+ TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TEST_HA_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
new file mode 100644
index 000000000000..e2d0e721f180
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_LTL
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_LTL_PARENT
+ default y
+ select LTL_MON_EVENTS_ID
+ bool "test_ltl monitor"
+ help
+ Simple description
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
new file mode 100644
index 000000000000..dd961c5dc8ad
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ltl"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/ltl_parent/ltl_parent.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_ltl.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_ltl(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_ltl(void)
+{
+ rv_detach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ltl",
+ .description = "Simple description",
+ .enable = enable_test_ltl,
+ .disable = disable_test_ltl,
+};
+
+static int __init register_test_ltl(void)
+{
+ return rv_register_monitor(&rv_this, &rv_ltl_parent);
+}
+
+static void __exit unregister_test_ltl(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ltl);
+module_exit(unregister_test_ltl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ltl: Simple description");
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
new file mode 100644
index 000000000000..7895f2e233e8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_ltl
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
new file mode 100644
index 000000000000..3571b004c114
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_LTL
+DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_LTL */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
new file mode 100644
index 000000000000..3e334c344261
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_LTL_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "test_ltl_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
new file mode 100644
index 000000000000..c1d58ce435a8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ltl_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_ltl_kunit.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_ltl_kunit(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_ltl_kunit(void)
+{
+ rv_detach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ltl_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_ltl_kunit,
+ .disable = disable_test_ltl_kunit,
+};
+
+static int __init register_test_ltl_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ltl_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ltl_kunit);
+module_exit(unregister_test_ltl_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ltl_kunit: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
new file mode 100644
index 000000000000..acc503b56e87
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_ltl_kunit
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
new file mode 100644
index 000000000000..37dab5dfdebc
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_ltl_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_LTL_KUNIT)
+
+static void rv_test_test_ltl_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_ltl_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_ltl_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_ltl_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
new file mode 100644
index 000000000000..b2ca34be327f
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_LTL_KUNIT_KUNIT_H
+#define __TEST_LTL_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_ltl_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_example_event)(void *data, /* XXX: fill header */);
+} rv_test_ltl_kunit_ops;
+#endif
+
+#endif /* __TEST_LTL_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
new file mode 100644
index 000000000000..a054d5b2c0ea
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_LTL_KUNIT
+DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl_kunit,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl_kunit,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_LTL_KUNIT */
diff --git a/tools/verification/rvgen/tests/rvgen_container.t b/tools/verification/rvgen/tests/rvgen_container.t
new file mode 100644
index 000000000000..fa4fb3db8288
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_container.t
@@ -0,0 +1,20 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help tests
+check "verify container subcommand help" \
+ "$RVGEN container -h" 0 "model_name" "class"
+
+check_and_compare_folder "container with description" \
+ "$RVGEN container -n test_container -D 'Test container for grouping monitors'" \
+ "test_container" "Writing the monitor into the directory test_container"
+
+# Error handling tests
+check "missing required model_name" \
+ "$RVGEN container" 2 "the following arguments are required: -n/--model_name"
+
+test_end
diff --git a/tools/verification/rvgen/tests/rvgen_kunit.t b/tools/verification/rvgen/tests/rvgen_kunit.t
new file mode 100644
index 000000000000..d27d9175f562
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_kunit.t
@@ -0,0 +1,41 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help tests
+check "verify kunit subcommand help" \
+ "$RVGEN kunit -h" 0 "model_name" "spec"
+
+check_and_compare_folder "KUnit generation with local lookup and test_da_kunit" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu -n test_da_kunit && $RVGEN kunit -a -l -n test_da_kunit" \
+ "test_da_kunit" "Now complete the test and add it to rv_monitors_test.c" "RV_MON_OPS_INIT"
+
+check_and_compare_folder "KUnit generation with local lookup and test_ha_kunit" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task -n test_ha_kunit && $RVGEN kunit -a -l -n test_ha_kunit" \
+ "test_ha_kunit" "Successfully created KUnit" "Append the following to"
+
+check_and_compare_folder "KUnit generation with local lookup and test_ltl_kunit" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_ltl_kunit && $RVGEN kunit -l -n test_ltl_kunit" \
+ "test_ltl_kunit" "RV_MON_OPS_INIT"
+
+check_and_compare_folder "KUnit generation with backup file" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_bak_kunit && echo DUMMY > test_bak_kunit/test_bak_kunit_kunit.c && $RVGEN kunit -l -n test_bak_kunit" \
+ "test_bak_kunit" "KUnit file(s) already exist.*backing up existing files"
+
+# Error handling tests
+check "missing required model_name" \
+ "$RVGEN kunit" 2 "the following arguments are required: -n/--model_name"
+
+check "non-existent model_name with auto_patch" \
+ "$RVGEN kunit -a -n nonexistent" 1 \
+ "Could not find monitor C file" "Traceback (most recent call last)"
+
+check "monitor without handlers" \
+ "mkdir -p nohandler ; echo DUMMY > nohandler/nohandler.c ; $RVGEN kunit -l -n nohandler" 1 \
+ "No handlers found" "Traceback (most recent call last)"
+rm -rf nohandler
+
+test_end
diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t
new file mode 100644
index 000000000000..5f2562600bad
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_monitor.t
@@ -0,0 +1,87 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help and basic tests
+check "verify help page" \
+ "$RVGEN --help" 0 "Generate kernel rv monitor"
+
+check "verify monitor subcommand help" \
+ "$RVGEN monitor --help" 0 "Monitor class"
+
+# DA monitor tests - test all monitor types
+check_and_compare_folder "DA per_cpu (default name)" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu" \
+ "test_da" "obj-\$(CONFIG_RV_MON_TEST_DA) += monitors/test_da/test_da.o"
+
+check_and_compare_folder "DA global type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t global -n da_global" \
+ "da_global" "DA_MON_EVENTS_IMPLICIT"
+
+check_and_compare_folder "DA per_task with description" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_task -n da_pertask_desc -D 'Custom description for testing'" \
+ "da_pertask_desc" "#include <monitors/da_pertask_desc/da_pertask_desc_trace.h>"
+
+check_and_compare_folder "DA per_obj with parent" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_obj -n da_perobj_parent -p parent_mon" \
+ "da_perobj_parent" "DA_MON_EVENTS_ID"
+
+# HA monitor tests
+check_and_compare_folder "HA per_task (default name)" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task" \
+ "test_ha" "HA_MON_EVENTS_ID"
+
+check_and_compare_folder "HA per_cpu type" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_cpu -n ha_percpu" \
+ "ha_percpu" "HA_MON_EVENTS_IMPLICIT"
+
+# LTL monitor test
+check_and_compare_folder "LTL per_task" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n ltl_pertask" \
+ "ltl_pertask" "source \"kernel/trace/rv/monitors/ltl_pertask/Kconfig\""
+
+check_and_compare_folder "LTL per_task with parent and description (default name)" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -p ltl_parent -D 'Simple description'" \
+ "test_ltl" "LTL_MON_EVENTS_ID"
+
+# Error handling tests
+check "missing required spec argument" \
+ "$RVGEN monitor -c da -t per_cpu" 2 \
+ "the following arguments are required: -s/--spec" "Traceback (most recent call last)"
+
+check "missing required monitor type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot" 2 \
+ "the following arguments are required: -t/--monitor_type" "Traceback (most recent call last)"
+
+check "missing required monitor class" \
+ "$RVGEN monitor -s tests/specs/test_da.dot -t per_cpu" 2 \
+ "the following arguments are required: -c/--class" "Traceback (most recent call last)"
+
+check "invalid monitor class" \
+ "$RVGEN monitor -c invalid -s tests/specs/test_da.dot -t per_cpu" 1 \
+ "Unknown monitor class" "Traceback (most recent call last)"
+
+check "missing dot file" \
+ "$RVGEN monitor -c da -s tests/specs/nonexistent.dot -t per_cpu" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "missing ltl file" \
+ "$RVGEN monitor -c ltl -s tests/specs/nonexistent.ltl -t per_task" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "invalid dot file syntax" \
+ "$RVGEN monitor -c da -s tests/specs/test_invalid.dot -t per_cpu" 1 \
+ "The automaton doesn't have an initial state" "Traceback (most recent call last)"
+
+check "invalid ha file syntax" \
+ "$RVGEN monitor -c ha -s tests/specs/test_invalid_ha.dot -t per_obj" 1 \
+ "Unrecognised event" "Traceback (most recent call last)"
+
+check "invalid ltl file syntax" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_invalid.ltl -t per_task" 1 \
+ "No terminal matches 'i'" "Traceback (most recent call last)"
+
+test_end
diff --git a/tools/verification/rvgen/tests/specs/test_da.dot b/tools/verification/rvgen/tests/specs/test_da.dot
new file mode 100644
index 000000000000..e555c239b221
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_a" [ label = "event_2" ];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_da2.dot b/tools/verification/rvgen/tests/specs/test_da2.dot
new file mode 100644
index 000000000000..cdd4192f58ae
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da2.dot
@@ -0,0 +1,19 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = circle] "state_c"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_a" -> "state_c" [ label = "event_2" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ "state_b" -> "state_c" [ label = "event_3" ];
+ "state_c" [label = "state_c"];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ha.dot b/tools/verification/rvgen/tests/specs/test_ha.dot
new file mode 100644
index 000000000000..af18ad7389ec
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ha.dot
@@ -0,0 +1,27 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = circle] "S1"};
+ {node [shape = plaintext, style=invis, label=""] "__init_S0"};
+ {node [shape = doublecircle] "S0"};
+ {node [shape = circle] "S0"};
+ {node [shape = circle] "S2"};
+ {node [shape = circle] "S3"};
+ "__init_S0" -> "S0";
+ "S0" [label = "S0\nclk < bar_ns()", color = green3];
+ "S1" [label = "S1"];
+ "S2" [label = "S2\nclk < BAR_NS()"];
+ "S3" [label = "S3"];
+ "S1" -> "S0" [ label = "event0;reset(clk)" ];
+ "S0" -> "S1" [ label = "event1;reset(clk)" ];
+ "S0" -> "S0" [ label = "event0;reset(clk)" ];
+ "S1" -> "S2" [ label = "event2;env1 == 0;reset(clk)" ];
+ "S2" -> "S3" [ label = "event2" ];
+ "S2" -> "S2" [ label = "event1;clk < foo_ns" ];
+ "S3" -> "S0" [ label = "event0;clk < FOO_NS && env2 == 0" ];
+ "S3" -> "S1" [ label = "event1;clk < 5us && env1 == 1;reset(clk)" ];
+ { rank = min ;
+ "__init_S0";
+ "S0";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.dot b/tools/verification/rvgen/tests/specs/test_invalid.dot
new file mode 100644
index 000000000000..17c63fc57f17
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.dot
@@ -0,0 +1,8 @@
+digraph invalid {
+ {node [shape = circle] "init"};
+ {node [shape = circle] "state1"};
+ "init" [label = "init"];
+ "init" -> "state1" [ label = "event_a" ];
+ "state1" [label = "state1"];
+ "state1" -> "init" [ label = "event_b" ];
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.ltl b/tools/verification/rvgen/tests/specs/test_invalid.ltl
new file mode 100644
index 000000000000..cf36307e003c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.ltl
@@ -0,0 +1 @@
+RULE = A invalid B
diff --git a/tools/verification/rvgen/tests/specs/test_invalid_ha.dot b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
new file mode 100644
index 000000000000..06de6aa8709f
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a;clk < 1"];
+ "state_a" -> "state_a" [ label = "event_2;reset(clk)" ];
+ "state_a" -> "state_b" [ label = "event_1;wrong_constraint" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ltl.ltl b/tools/verification/rvgen/tests/specs/test_ltl.ltl
new file mode 100644
index 000000000000..5ed658abd69c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ltl.ltl
@@ -0,0 +1 @@
+RULE = always (EVENT_A imply eventually EVENT_B)
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
new file mode 100644
index 000000000000..cfdf2180aad8
--- /dev/null
+++ b/tools/verification/tests/engine.sh
@@ -0,0 +1,175 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+test_begin() {
+ # Count tests to allow the test harness to double-check if all were
+ # included correctly.
+ ctr=0
+ [ -z "$RV" ] && RV="../rv/rv"
+ [ -z "$RVGEN" ] && RVGEN="python3 ../rvgen"
+ [ -z "$GOLDEN_DIR" ] && GOLDEN_DIR="tests/golden"
+ [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
+}
+
+failure() {
+ fail=1
+ if [ $# -gt 0 ]; then
+ failbuf+="$1"
+ failbuf+=$'\n'
+ fi
+}
+
+report() {
+ local desc="$1"
+
+ if [ "$fail" -eq 0 ]; then
+ echo "ok $ctr - $desc"
+ else
+ # Add output and exit code as comments in case of failure
+ echo "not ok $ctr - $desc"
+ echo -n "$failbuf"
+ echo "$result" | col -b | while read -r line; do echo "# $line"; done
+ printf "#\n# exit code %s\n" "$exitcode"
+ fi
+}
+
+_check() {
+ local command=$2
+ local expected_exitcode=${3:-0}
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+ local patterns="$expected_output $unexpected_output $all_lines_pattern"
+ local bgpid pid
+
+ eval "$TIMEOUT" "$command" &> check_output.$$ &
+ bgpid=$!
+
+ if grep -q "\$pid" <<< "$patterns"; then
+ for _ in {1..30}; do
+ pid=$(pgrep -f "${command%%[|;&>]*}" | tail -n1)
+ [ -n "$pid" ] && break
+ sleep 0.1
+ done
+ fi
+
+ wait $bgpid
+ exitcode=$?
+ result=$(tr -d '\0' < check_output.$$)
+ rm -f check_output.$$
+
+ failbuf=''
+ fail=0
+
+ # Suppress any other error if a needed pid is empty
+ if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns"; then
+ result=''
+ failure "# Empty pid for $command"
+ return 1
+ fi
+
+ expected_output="${expected_output//\$pid/$pid}"
+ unexpected_output="${unexpected_output//\$pid/$pid}"
+ all_lines_pattern="${all_lines_pattern//\$pid/$pid}"
+
+ # Test if the results matches if requested
+ if [ -n "$expected_output" ] && ! grep -qe "$expected_output" <<< "$result"; then
+ failure "# Output match failed: \"$expected_output\""
+ fi
+
+ if [ -n "$unexpected_output" ] && grep -qe "$unexpected_output" <<< "$result"; then
+ failure "# Output non-match failed: \"$unexpected_output\""
+ fi
+
+ if [ -n "$all_lines_pattern" ] && grep -vqe "$all_lines_pattern" <<< "$result"; then
+ failure "# All-lines pattern failed: \"$all_lines_pattern\""
+ fi
+
+ if [ $exitcode -ne "$expected_exitcode" ]; then
+ failure "# Expected exit code $expected_exitcode"
+ fi
+}
+
+check() {
+ # Simple check: run the command with given arguments and test exit code.
+ # If TEST_COUNT is set, run the test. Otherwise, just count.
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ _check "$@"
+ report "$1"
+ fi
+}
+
+check_if_exists() {
+ # Conditional check that skips if a file or folder doesn't exist
+ local desc=$1
+ local command=$2
+ local file=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ if [ ! -e "$file" ]; then
+ echo "ok $ctr - $desc # SKIP file not found: $file"
+ else
+ _check "$desc" "$command" 0 "$expected_output" \
+ "$unexpected_output" "$all_lines_pattern"
+ report "$desc"
+ fi
+ fi
+}
+
+check_and_compare_folder() {
+ # Run command, compare generated folder to golden, and cleanup
+ local desc=$1
+ local command=$2
+ local generated_dir=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local golden_dir="$GOLDEN_DIR/$generated_dir"
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ rm -rf "$generated_dir"
+ _check "$desc" "$command" 0 "$expected_output" "$unexpected_output"
+
+ if [ "$fail" -eq 0 ] && [ ! -d "$generated_dir" ]; then
+ failure "# Generated directory not found: $generated_dir"
+ fi
+
+ if [ "$fail" -ne 0 ]; then
+ :
+ elif ! diff -r "$generated_dir" "$golden_dir" &> /dev/null; then
+ failure "# Directories differ:"
+ failbuf+=$(diff -r "$generated_dir" "$golden_dir" 2>&1 | sed 's/^/# /')
+ failbuf+=$'\n'
+ fi
+
+ report "$1"
+
+ rm -rf "$generated_dir"
+ fi
+}
+
+set_timeout() {
+ TIMEOUT="timeout -v -k 30s $1"
+}
+
+set_expected_timeout() {
+ TIMEOUT="timeout --preserve-status -k 30s $1"
+}
+
+unset_timeout() {
+ unset TIMEOUT
+}
+
+test_end() {
+ # If running without TEST_COUNT, tests are not actually run, just
+ # counted. In that case, re-run the test with the correct count.
+ [ -z "$TEST_COUNT" ] && TEST_COUNT=$ctr exec bash "$0" || true
+}
+
+# Avoid any environmental discrepancies
+export LC_ALL=C
+unset_timeout