linux-trace-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors
@ 2025-09-19 14:09 Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros Gabriele Monaco
                   ` (20 more replies)
  0 siblings, 21 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Nam Cao
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur, linux-trace-kernel

This RFC contains several related changes, I'm sending them together to
give the full picture and get early feedback but I'm probably going to
submit some of them separately.
The main areas are:

* DA monitor cleanup to remove macros

* Hybrid automata

    Hybrid automata are an extension of deterministic automata where each
    state transition is validating a constraint on a finite number of
    environment variables.

    Hybrid automata can be used to implement timed automata, where the
    environment variables are clocks.

* per-object monitors

    Define the generic per-object monitor allow RV monitors on any kind
    of object where the user can specify how to get an id (e.g. pid for
    tasks) and the data type for the monitor_target (e.g. struct
    task_struct * for tasks).

    The monitor storage (e.g. the rv monitor, pointer to the target, etc.)
    is stored in a hash table indexed by id.

* deadline monitors collection

    Add the throttle and nomiss monitors to validate timing aspects of
    the deadline scheduler, as they work for tasks and servers, their
    inclusion requires also per-object monitors (for dl entities).

Note: this series is NOT based on the changes in [1], the next version
will integrate that and likely adapt models.

Changes since V1 [2]:
* Cleanup unused trace events definitions
* Improve hybrid automata description about use of timers
* Unify event handler internals across DA monitor types
* Implement timer wheel alternative for invariants
* Extend models to consider timing of task switches (in before deadline,
  out after throttle)
* Refactor tracepoints and per-object monitors to allow server events
  from remote CPUs
* Changed clock representation in case of invariants (time to expire vs
  time since reset) and allow conversion without reset
* Extend dot2k to understand the graph relations and suggest where
  invariant conversions are needed
* General cleanup of rvgen scripts

[1] - https://lore.kernel.org/lkml/175817861437.709179.10913499403372809816.tip-bot2@tip-bot2
[2] - https://lore.kernel.org/lkml/20250814150809.140739-1-gmonaco@redhat.com

To: Steven Rostedt <rostedt@goodmis.org>
To: Nam Cao <namcao@linutronix.de>
Cc: Tomas Glozar <tglozar@redhat.com>
Cc: Juri Lelli <jlelli@redhat.com>
Cc: Clark Williams <williams@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: linux-trace-kernel@vger.kernel.org

Gabriele Monaco (20):
  rv: Refactor da_monitor to minimise macros
  rv: Cleanup da_monitor after refactor
  rv: Unify DA event handling functions across monitor types
  Documentation/rv: Adapt documentation after da_monitor refactoring
  verification/rvgen: Adapt dot2k and templates after refactoring
    da_monitor.h
  verification/rvgen: Annotate DA functions with types
  verification/dot2c: Remove __buff_to_string() and cleanup
  verification/dot2c: Remove superfluous enum assignment and add last
    comma
  verification/rvgen: Remove unused variable declaration from containers
  rv: Add Hybrid Automata monitor type
  verification/rvgen: Allow spaces in and events strings
  verification/rvgen: Add support for Hybrid Automata
  Documentation/rv: Add documentation about hybrid automata
  rv: Add sample hybrid monitors stall
  rv: Convert the opid monitor to a hybrid automaton
  sched: Export hidden tracepoints to modules
  sched: Add deadline tracepoints
  rv: Add support for per-object monitors in DA/HA
  verification/rvgen: Add support for per-obj monitors
  rv: Add deadline monitors

 Documentation/tools/rv/index.rst              |    1 +
 Documentation/tools/rv/rv-mon-stall.rst       |   44 +
 .../trace/rv/deterministic_automata.rst       |    2 +-
 Documentation/trace/rv/hybrid_automata.rst    |  340 +++++
 Documentation/trace/rv/index.rst              |    3 +
 Documentation/trace/rv/monitor_deadline.rst   |  151 ++
 Documentation/trace/rv/monitor_sched.rst      |   62 +-
 Documentation/trace/rv/monitor_stall.rst      |   43 +
 Documentation/trace/rv/monitor_synthesis.rst  |  151 +-
 include/linux/rv.h                            |   43 +
 include/rv/automata.h                         |  132 +-
 include/rv/da_monitor.h                       | 1289 ++++++++++-------
 include/rv/ha_monitor.h                       |  472 ++++++
 include/trace/events/sched.h                  |   16 +
 kernel/sched/core.c                           |    7 +
 kernel/sched/deadline.c                       |    9 +
 kernel/trace/rv/Kconfig                       |   19 +
 kernel/trace/rv/Makefile                      |    4 +
 kernel/trace/rv/monitors/deadline/Kconfig     |   10 +
 kernel/trace/rv/monitors/deadline/deadline.c  |   35 +
 kernel/trace/rv/monitors/deadline/deadline.h  |  200 +++
 kernel/trace/rv/monitors/nomiss/Kconfig       |   15 +
 kernel/trace/rv/monitors/nomiss/nomiss.c      |  283 ++++
 kernel/trace/rv/monitors/nomiss/nomiss.h      |  130 ++
 .../trace/rv/monitors/nomiss/nomiss_trace.h   |   19 +
 kernel/trace/rv/monitors/nrp/nrp.c            |   30 +-
 kernel/trace/rv/monitors/nrp/nrp.h            |   22 +-
 kernel/trace/rv/monitors/opid/Kconfig         |   11 +-
 kernel/trace/rv/monitors/opid/opid.c          |  129 +-
 kernel/trace/rv/monitors/opid/opid.h          |   94 +-
 kernel/trace/rv/monitors/opid/opid_trace.h    |    4 +
 kernel/trace/rv/monitors/rtapp/rtapp.c        |    2 -
 kernel/trace/rv/monitors/sched/sched.c        |    2 -
 kernel/trace/rv/monitors/sco/sco.c            |   26 +-
 kernel/trace/rv/monitors/sco/sco.h            |   14 +-
 kernel/trace/rv/monitors/scpd/scpd.c          |   28 +-
 kernel/trace/rv/monitors/scpd/scpd.h          |   14 +-
 kernel/trace/rv/monitors/snep/snep.c          |   28 +-
 kernel/trace/rv/monitors/snep/snep.h          |   18 +-
 kernel/trace/rv/monitors/snroc/snroc.c        |   26 +-
 kernel/trace/rv/monitors/snroc/snroc.h        |   14 +-
 kernel/trace/rv/monitors/sssw/sssw.c          |   38 +-
 kernel/trace/rv/monitors/sssw/sssw.h          |   22 +-
 kernel/trace/rv/monitors/stall/Kconfig        |   13 +
 kernel/trace/rv/monitors/stall/stall.c        |  146 ++
 kernel/trace/rv/monitors/stall/stall.h        |   64 +
 kernel/trace/rv/monitors/stall/stall_trace.h  |   19 +
 kernel/trace/rv/monitors/sts/sts.c            |   34 +-
 kernel/trace/rv/monitors/sts/sts.h            |   28 +-
 kernel/trace/rv/monitors/throttle/Kconfig     |   15 +
 kernel/trace/rv/monitors/throttle/throttle.c  |  253 ++++
 kernel/trace/rv/monitors/throttle/throttle.h  |  118 ++
 .../rv/monitors/throttle/throttle_trace.h     |   19 +
 kernel/trace/rv/monitors/wip/wip.c            |   26 +-
 kernel/trace/rv/monitors/wip/wip.h            |   14 +-
 kernel/trace/rv/monitors/wwnr/wwnr.c          |   28 +-
 kernel/trace/rv/monitors/wwnr/wwnr.h          |   14 +-
 kernel/trace/rv/rv_trace.h                    |   68 +-
 tools/verification/models/deadline/nomiss.dot |   39 +
 .../verification/models/deadline/throttle.dot |   44 +
 tools/verification/models/sched/opid.dot      |   36 +-
 tools/verification/models/stall.dot           |   20 +
 tools/verification/rvgen/__main__.py          |    8 +-
 tools/verification/rvgen/rvgen/automata.py    |  166 ++-
 tools/verification/rvgen/rvgen/dot2c.py       |  160 +-
 tools/verification/rvgen/rvgen/dot2k.py       |  506 ++++++-
 tools/verification/rvgen/rvgen/generator.py   |    4 +-
 .../rvgen/rvgen/templates/container/main.c    |    2 -
 .../rvgen/rvgen/templates/dot2k/main.c        |   25 +-
 .../rvgen/templates/dot2k/trace_hybrid.h      |   16 +
 70 files changed, 4717 insertions(+), 1170 deletions(-)
 create mode 100644 Documentation/tools/rv/rv-mon-stall.rst
 create mode 100644 Documentation/trace/rv/hybrid_automata.rst
 create mode 100644 Documentation/trace/rv/monitor_deadline.rst
 create mode 100644 Documentation/trace/rv/monitor_stall.rst
 create mode 100644 include/rv/ha_monitor.h
 create mode 100644 kernel/trace/rv/monitors/deadline/Kconfig
 create mode 100644 kernel/trace/rv/monitors/deadline/deadline.c
 create mode 100644 kernel/trace/rv/monitors/deadline/deadline.h
 create mode 100644 kernel/trace/rv/monitors/nomiss/Kconfig
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.c
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.h
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_trace.h
 create mode 100644 kernel/trace/rv/monitors/stall/Kconfig
 create mode 100644 kernel/trace/rv/monitors/stall/stall.c
 create mode 100644 kernel/trace/rv/monitors/stall/stall.h
 create mode 100644 kernel/trace/rv/monitors/stall/stall_trace.h
 create mode 100644 kernel/trace/rv/monitors/throttle/Kconfig
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle.c
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle.h
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle_trace.h
 create mode 100644 tools/verification/models/deadline/nomiss.dot
 create mode 100644 tools/verification/models/deadline/throttle.dot
 create mode 100644 tools/verification/models/stall.dot
 create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h


base-commit: 8b789f2b7602a818e7c7488c74414fae21392b63
-- 
2.51.0


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

* [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  8:45   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 02/20] rv: Cleanup da_monitor after refactor Gabriele Monaco
                   ` (19 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The da_monitor helper functions are generated from macros of the type:

DECLARE_DA_FUNCTION(name, type) \
static void da_func_x_##name(type arg) {} \
static void da_func_y_##name(type arg) {} \

This is good to minimise code duplication but the long macros made of
skipped end of lines is rather hard to parse. Since functions are
static, the advantage of naming them differently for each monitor is
minimal.

Refactor the da_monitor.h file to minimise macros, instead of declaring
functions from macros, we simply declare them with the same name for all
monitors (e.g. da_func_x) and for any remaining reference to the monitor
name (e.g. tracepoints, enums, global variables) we use the CONCATENATE
macro.
In this way the file is much easier to maintain while keeping the same
generality.
Functions depending on the monitor types are now conditionally compiled
according to the value of RV_MON_TYPE, which must be defined in the
monitor source.
The monitor type can be specified as in the original implementation,
although it's best to keep the default implementation (unsigned char) as
not all parts of code support larger data types, and likely there's no
need.

We keep the empty macro definitions to ease review of this change with
diff tools, but cleanup is required.

Also adapt existing monitors to keep the build working.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/linux/rv.h                     |   4 +
 include/rv/automata.h                  | 132 ++--
 include/rv/da_monitor.h                | 911 ++++++++++++-------------
 kernel/trace/rv/monitors/nrp/nrp.c     |  30 +-
 kernel/trace/rv/monitors/nrp/nrp.h     |   2 +
 kernel/trace/rv/monitors/opid/opid.c   |  40 +-
 kernel/trace/rv/monitors/opid/opid.h   |   2 +
 kernel/trace/rv/monitors/sco/sco.c     |  26 +-
 kernel/trace/rv/monitors/sco/sco.h     |   2 +
 kernel/trace/rv/monitors/scpd/scpd.c   |  28 +-
 kernel/trace/rv/monitors/scpd/scpd.h   |   2 +
 kernel/trace/rv/monitors/snep/snep.c   |  28 +-
 kernel/trace/rv/monitors/snep/snep.h   |   2 +
 kernel/trace/rv/monitors/snroc/snroc.c |  26 +-
 kernel/trace/rv/monitors/snroc/snroc.h |   2 +
 kernel/trace/rv/monitors/sssw/sssw.c   |  38 +-
 kernel/trace/rv/monitors/sssw/sssw.h   |   2 +
 kernel/trace/rv/monitors/sts/sts.c     |  34 +-
 kernel/trace/rv/monitors/sts/sts.h     |   2 +
 kernel/trace/rv/monitors/wip/wip.c     |  26 +-
 kernel/trace/rv/monitors/wip/wip.h     |   2 +
 kernel/trace/rv/monitors/wwnr/wwnr.c   |  28 +-
 kernel/trace/rv/monitors/wwnr/wwnr.h   |   2 +
 23 files changed, 689 insertions(+), 682 deletions(-)

diff --git a/include/linux/rv.h b/include/linux/rv.h
index 14410a42faef..3134681553b4 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -13,6 +13,10 @@
 #define MAX_DA_NAME_LEN			32
 #define MAX_DA_RETRY_RACING_EVENTS	3
 
+#define RV_MON_GLOBAL   0
+#define RV_MON_PER_CPU  1
+#define RV_MON_PER_TASK 2
+
 #ifdef CONFIG_RV
 #include <linux/bitops.h>
 #include <linux/types.h>
diff --git a/include/rv/automata.h b/include/rv/automata.h
index eb9e636809a0..5b5d2e94c034 100644
--- a/include/rv/automata.h
+++ b/include/rv/automata.h
@@ -6,6 +6,20 @@
  * models in C generated by the dot2k tool.
  */
 
+#ifndef MONITOR_NAME
+#error "MONITOR_NAME macro is not defined. Did you include $(MODEL_NAME).h generated by rvgen?"
+#endif
+
+#ifndef type
+#define type unsigned char
+#endif
+
+#define RV_AUTOMATON_NAME CONCATENATE(automaton_, MONITOR_NAME)
+#define EVENT_MAX CONCATENATE(event_max_, MONITOR_NAME)
+#define STATE_MAX CONCATENATE(state_max_, MONITOR_NAME)
+#define events CONCATENATE(events_, MONITOR_NAME)
+#define states CONCATENATE(states_, MONITOR_NAME)
+
 /*
  * DECLARE_AUTOMATA_HELPERS - define a set of helper functions for automata
  *
@@ -13,63 +27,63 @@
  * as suffix for the functions and data. These functions will handle automaton
  * with data type 'type'.
  */
-#define DECLARE_AUTOMATA_HELPERS(name, type)					\
-										\
-/*										\
- * model_get_state_name_##name - return the (string) name of the given state	\
- */ 										\
-static char *model_get_state_name_##name(enum states_##name state)		\
-{										\
-	if ((state < 0) || (state >= state_max_##name))				\
-		return "INVALID";						\
-										\
-	return automaton_##name.state_names[state];				\
-}										\
-										\
-/*										\
- * model_get_event_name_##name - return the (string) name of the given event	\
- */										\
-static char *model_get_event_name_##name(enum events_##name event)		\
-{										\
-	if ((event < 0) || (event >= event_max_##name))				\
-		return "INVALID";						\
-										\
-	return automaton_##name.event_names[event];				\
-}										\
-										\
-/*										\
- * model_get_initial_state_##name - return the automaton's initial state		\
- */										\
-static inline type model_get_initial_state_##name(void)				\
-{										\
-	return automaton_##name.initial_state;					\
-}										\
-										\
-/*										\
- * model_get_next_state_##name - process an automaton event occurrence		\
- *										\
- * Given the current state (curr_state) and the event (event), returns		\
- * the next state, or INVALID_STATE in case of error.				\
- */										\
-static inline type model_get_next_state_##name(enum states_##name curr_state,	\
-					       enum events_##name event)	\
-{										\
-	if ((curr_state < 0) || (curr_state >= state_max_##name))		\
-		return INVALID_STATE;						\
-										\
-	if ((event < 0) || (event >= event_max_##name))				\
-		return INVALID_STATE;						\
-										\
-	return automaton_##name.function[curr_state][event];			\
-}										\
-										\
-/*										\
- * model_is_final_state_##name - check if the given state is a final state	\
- */										\
-static inline bool model_is_final_state_##name(enum states_##name state)	\
-{										\
-	if ((state < 0) || (state >= state_max_##name))				\
-		return 0;							\
-										\
-	return automaton_##name.final_states[state];				\
+#define DECLARE_AUTOMATA_HELPERS(name, type)
+
+/*
+ * model_get_state_name - return the (string) name of the given state
+ */
+static char *model_get_state_name(enum states state)
+{
+	if ((state < 0) || (state >= STATE_MAX))
+		return "INVALID";
+
+	return RV_AUTOMATON_NAME.state_names[state];
+}
+
+/*
+ * model_get_event_name - return the (string) name of the given event
+ */
+static char *model_get_event_name(enum events event)
+{
+	if ((event < 0) || (event >= EVENT_MAX))
+		return "INVALID";
+
+	return RV_AUTOMATON_NAME.event_names[event];
+}
+
+/*
+ * model_get_initial_state - return the automaton's initial state
+ */
+static inline type model_get_initial_state(void)
+{
+	return RV_AUTOMATON_NAME.initial_state;
+}
+
+/*
+ * model_get_next_state - process an automaton event occurrence
+ *
+ * Given the current state (curr_state) and the event (event), returns
+ * the next state, or INVALID_STATE in case of error.
+ */
+static inline type model_get_next_state(enum states curr_state,
+					       enum events event)
+{
+	if ((curr_state < 0) || (curr_state >= STATE_MAX))
+		return INVALID_STATE;
+
+	if ((event < 0) || (event >= EVENT_MAX))
+		return INVALID_STATE;
+
+	return RV_AUTOMATON_NAME.function[curr_state][event];
+}
+
+/*
+ * model_is_final_state - check if the given state is a final state
+ */
+static inline bool model_is_final_state(enum states state)
+{
+	if ((state < 0) || (state >= STATE_MAX))
+		return 0;
+
+	return RV_AUTOMATON_NAME.final_states[state];
 }
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 17fa4f6e5ea6..c2bc1350bb40 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -13,97 +13,101 @@
 
 #include <rv/automata.h>
 #include <linux/rv.h>
+#include <linux/stringify.h>
 #include <linux/bug.h>
 #include <linux/sched.h>
 
+#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
+static struct rv_monitor rv_this;
+
 #ifdef CONFIG_RV_REACTORS
 
-#define DECLARE_RV_REACTING_HELPERS(name, type)							\
-static void cond_react_##name(type curr_state, type event)					\
-{												\
-	if (!rv_reacting_on() || !rv_##name.react)						\
-		return;										\
-	rv_##name.react("rv: monitor %s does not allow event %s on state %s\n",			\
-			#name,									\
-			model_get_event_name_##name(event),					\
-			model_get_state_name_##name(curr_state));				\
+#define DECLARE_RV_REACTING_HELPERS(name, type)
+static void cond_react(type curr_state, type event)
+{
+	if (!rv_reacting_on() || !rv_this.react)
+		return;
+	rv_this.react("rv: monitor %s does not allow event %s on state %s\n",
+			__stringify(MONITOR_NAME),
+			model_get_event_name(event),
+			model_get_state_name(curr_state));
 }
 
 #else /* CONFIG_RV_REACTOR */
 
-#define DECLARE_RV_REACTING_HELPERS(name, type)							\
-static void cond_react_##name(type curr_state, type event)					\
-{												\
-	return;											\
+#define DECLARE_RV_REACTING_HELPERS(name, type)
+static void cond_react(type curr_state, type event)
+{
+	return;
 }
 #endif
 
 /*
  * Generic helpers for all types of deterministic automata monitors.
  */
-#define DECLARE_DA_MON_GENERIC_HELPERS(name, type)						\
-												\
-DECLARE_RV_REACTING_HELPERS(name, type)								\
-												\
-/*												\
- * da_monitor_reset_##name - reset a monitor and setting it to init state			\
- */												\
-static inline void da_monitor_reset_##name(struct da_monitor *da_mon)				\
-{												\
-	da_mon->monitoring = 0;									\
-	da_mon->curr_state = model_get_initial_state_##name();					\
-}												\
-												\
-/*												\
- * da_monitor_start_##name - start monitoring							\
- *												\
- * The monitor will ignore all events until monitoring is set to true. This			\
- * function needs to be called to tell the monitor to start monitoring.				\
- */												\
-static inline void da_monitor_start_##name(struct da_monitor *da_mon)				\
-{												\
-	da_mon->curr_state = model_get_initial_state_##name();					\
-	da_mon->monitoring = 1;									\
-}												\
-												\
-/*												\
- * da_monitoring_##name - returns true if the monitor is processing events			\
- */												\
-static inline bool da_monitoring_##name(struct da_monitor *da_mon)				\
-{												\
-	return da_mon->monitoring;								\
-}												\
-												\
-/*												\
- * da_monitor_enabled_##name - checks if the monitor is enabled					\
- */												\
-static inline bool da_monitor_enabled_##name(void)						\
-{												\
-	/* global switch */									\
-	if (unlikely(!rv_monitoring_on()))							\
-		return 0;									\
-												\
-	/* monitor enabled */									\
-	if (unlikely(!rv_##name.enabled))							\
-		return 0;									\
-												\
-	return 1;										\
-}												\
-												\
-/*												\
- * da_monitor_handling_event_##name - checks if the monitor is ready to handle events		\
- */												\
-static inline bool da_monitor_handling_event_##name(struct da_monitor *da_mon)			\
-{												\
-												\
-	if (!da_monitor_enabled_##name())							\
-		return 0;									\
-												\
-	/* monitor is actually monitoring */							\
-	if (unlikely(!da_monitoring_##name(da_mon)))						\
-		return 0;									\
-												\
-	return 1;										\
+#define DECLARE_DA_MON_GENERIC_HELPERS(name, type)
+
+DECLARE_RV_REACTING_HELPERS(name, type)
+
+/*
+ * da_monitor_reset - reset a monitor and setting it to init state
+ */
+static inline void da_monitor_reset(struct da_monitor *da_mon)
+{
+	da_mon->monitoring = 0;
+	da_mon->curr_state = model_get_initial_state();
+}
+
+/*
+ * da_monitor_start - start monitoring
+ *
+ * The monitor will ignore all events until monitoring is set to true. This
+ * function needs to be called to tell the monitor to start monitoring.
+ */
+static inline void da_monitor_start(struct da_monitor *da_mon)
+{
+	da_mon->curr_state = model_get_initial_state();
+	da_mon->monitoring = 1;
+}
+
+/*
+ * da_monitoring - returns true if the monitor is processing events
+ */
+static inline bool da_monitoring(struct da_monitor *da_mon)
+{
+	return da_mon->monitoring;
+}
+
+/*
+ * da_monitor_enabled - checks if the monitor is enabled
+ */
+static inline bool da_monitor_enabled(void)
+{
+	/* global switch */
+	if (unlikely(!rv_monitoring_on()))
+		return 0;
+
+	/* monitor enabled */
+	if (unlikely(!rv_this.enabled))
+		return 0;
+
+	return 1;
+}
+
+/*
+ * da_monitor_handling_event - checks if the monitor is ready to handle events
+ */
+static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
+{
+
+	if (!da_monitor_enabled())
+		return 0;
+
+	/* monitor is actually monitoring */
+	if (unlikely(!da_monitoring(da_mon)))
+		return 0;
+
+	return 1;
 }
 
 /*
@@ -115,37 +119,37 @@ static inline bool da_monitor_handling_event_##name(struct da_monitor *da_mon)
  * warn and reset the monitor if it runs out of retries. The monitor should be
  * able to handle various orders.
  */
-#define DECLARE_DA_MON_MODEL_HANDLER_IMPLICIT(name, type)					\
-												\
-static inline bool										\
-da_event_##name(struct da_monitor *da_mon, enum events_##name event)				\
-{												\
-	enum states_##name curr_state, next_state;						\
-												\
-	curr_state = READ_ONCE(da_mon->curr_state);						\
-	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {					\
-		next_state = model_get_next_state_##name(curr_state, event);			\
-		if (next_state == INVALID_STATE) {						\
-			cond_react_##name(curr_state, event);					\
-			trace_error_##name(model_get_state_name_##name(curr_state),		\
-					   model_get_event_name_##name(event));			\
-			return false;								\
-		}										\
-		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {	\
-			trace_event_##name(model_get_state_name_##name(curr_state),		\
-					   model_get_event_name_##name(event),			\
-					   model_get_state_name_##name(next_state),		\
-					   model_is_final_state_##name(next_state));		\
-			return true;								\
-		}										\
-	}											\
-												\
-	trace_rv_retries_error(#name, model_get_event_name_##name(event));			\
-	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)					\
-		" retries reached for event %s, resetting monitor %s",				\
-		model_get_event_name_##name(event), #name);					\
-	return false;										\
-}												\
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+
+static inline bool
+da_event(struct da_monitor *da_mon, enum events event)
+{
+	enum states curr_state, next_state;
+
+	curr_state = READ_ONCE(da_mon->curr_state);
+	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
+		next_state = model_get_next_state(curr_state, event);
+		if (next_state == INVALID_STATE) {
+			cond_react(curr_state, event);
+			CONCATENATE(trace_error_, MONITOR_NAME)(model_get_state_name(curr_state),
+					   model_get_event_name(event));
+			return false;
+		}
+		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+			CONCATENATE(trace_event_, MONITOR_NAME)(model_get_state_name(curr_state),
+					   model_get_event_name(event),
+					   model_get_state_name(next_state),
+					   model_is_final_state(next_state));
+			return true;
+		}
+	}
+
+	trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
+	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
+		" retries reached for event %s, resetting monitor %s",
+		model_get_event_name(event), __stringify(MONITOR_NAME));
+	return false;
+}
 
 /*
  * Event handler for per_task monitors.
@@ -154,395 +158,380 @@ da_event_##name(struct da_monitor *da_mon, enum events_##name event)				\
  * warn and reset the monitor if it runs out of retries. The monitor should be
  * able to handle various orders.
  */
-#define DECLARE_DA_MON_MODEL_HANDLER_PER_TASK(name, type)					\
-												\
-static inline bool da_event_##name(struct da_monitor *da_mon, struct task_struct *tsk,		\
-				   enum events_##name event)					\
-{												\
-	enum states_##name curr_state, next_state;						\
-												\
-	curr_state = READ_ONCE(da_mon->curr_state);						\
-	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {					\
-		next_state = model_get_next_state_##name(curr_state, event);			\
-		if (next_state == INVALID_STATE) {						\
-			cond_react_##name(curr_state, event);					\
-			trace_error_##name(tsk->pid,						\
-					   model_get_state_name_##name(curr_state),		\
-					   model_get_event_name_##name(event));			\
-			return false;								\
-		}										\
-		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {	\
-			trace_event_##name(tsk->pid,						\
-					   model_get_state_name_##name(curr_state),		\
-					   model_get_event_name_##name(event),			\
-					   model_get_state_name_##name(next_state),		\
-					   model_is_final_state_##name(next_state));		\
-			return true;								\
-		}										\
-	}											\
-												\
-	trace_rv_retries_error(#name, model_get_event_name_##name(event));			\
-	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)					\
-		" retries reached for event %s, resetting monitor %s",				\
-		model_get_event_name_##name(event), #name);					\
-	return false;										\
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+
+static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
+				   enum events event)
+{
+	enum states curr_state, next_state;
+
+	curr_state = READ_ONCE(da_mon->curr_state);
+	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
+		next_state = model_get_next_state(curr_state, event);
+		if (next_state == INVALID_STATE) {
+			cond_react(curr_state, event);
+			CONCATENATE(trace_error_, MONITOR_NAME)(tsk->pid,
+					   model_get_state_name(curr_state),
+					   model_get_event_name(event));
+			return false;
+		}
+		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+			CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
+					   model_get_state_name(curr_state),
+					   model_get_event_name(event),
+					   model_get_state_name(next_state),
+					   model_is_final_state(next_state));
+			return true;
+		}
+	}
+
+	trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
+	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
+		" retries reached for event %s, resetting monitor %s",
+		model_get_event_name(event), __stringify(MONITOR_NAME));
+	return false;
 }
+#endif
 
 /*
  * Functions to define, init and get a global monitor.
  */
-#define DECLARE_DA_MON_INIT_GLOBAL(name, type)							\
-												\
-/*												\
- * global monitor (a single variable)								\
- */												\
-static struct da_monitor da_mon_##name;								\
-												\
-/*												\
- * da_get_monitor_##name - return the global monitor address					\
- */												\
-static struct da_monitor *da_get_monitor_##name(void)						\
-{												\
-	return &da_mon_##name;									\
-}												\
-												\
-/*												\
- * da_monitor_reset_all_##name - reset the single monitor					\
- */												\
-static void da_monitor_reset_all_##name(void)							\
-{												\
-	da_monitor_reset_##name(da_get_monitor_##name());					\
-}												\
-												\
-/*												\
- * da_monitor_init_##name - initialize a monitor						\
- */												\
-static inline int da_monitor_init_##name(void)							\
-{												\
-	da_monitor_reset_all_##name();								\
-	return 0;										\
-}												\
-												\
-/*												\
- * da_monitor_destroy_##name - destroy the monitor						\
- */												\
-static inline void da_monitor_destroy_##name(void)						\
-{												\
-	return;											\
+#if RV_MON_TYPE == RV_MON_GLOBAL
+
+/*
+ * global monitor (a single variable)
+ */
+static struct da_monitor RV_DA_MON_NAME;
+
+/*
+ * da_get_monitor - return the global monitor address
+ */
+static struct da_monitor *da_get_monitor(void)
+{
+	return &RV_DA_MON_NAME;
+}
+
+/*
+ * da_monitor_reset_all - reset the single monitor
+ */
+static void da_monitor_reset_all(void)
+{
+	da_monitor_reset(da_get_monitor());
+}
+
+/*
+ * da_monitor_init - initialize a monitor
+ */
+static inline int da_monitor_init(void)
+{
+	da_monitor_reset_all();
+	return 0;
+}
+
+/*
+ * da_monitor_destroy - destroy the monitor
+ */
+static inline void da_monitor_destroy(void)
+{
+	return;
 }
 
 /*
  * Functions to define, init and get a per-cpu monitor.
  */
-#define DECLARE_DA_MON_INIT_PER_CPU(name, type)							\
-												\
-/*												\
- * per-cpu monitor variables									\
- */												\
-static DEFINE_PER_CPU(struct da_monitor, da_mon_##name);					\
-												\
-/*												\
- * da_get_monitor_##name - return current CPU monitor address					\
- */												\
-static struct da_monitor *da_get_monitor_##name(void)						\
-{												\
-	return this_cpu_ptr(&da_mon_##name);							\
-}												\
-												\
-/*												\
- * da_monitor_reset_all_##name - reset all CPUs' monitor					\
- */												\
-static void da_monitor_reset_all_##name(void)							\
-{												\
-	struct da_monitor *da_mon;								\
-	int cpu;										\
-	for_each_cpu(cpu, cpu_online_mask) {							\
-		da_mon = per_cpu_ptr(&da_mon_##name, cpu);					\
-		da_monitor_reset_##name(da_mon);						\
-	}											\
-}												\
-												\
-/*												\
- * da_monitor_init_##name - initialize all CPUs' monitor					\
- */												\
-static inline int da_monitor_init_##name(void)							\
-{												\
-	da_monitor_reset_all_##name();								\
-	return 0;										\
-}												\
-												\
-/*												\
- * da_monitor_destroy_##name - destroy the monitor						\
- */												\
-static inline void da_monitor_destroy_##name(void)						\
-{												\
-	return;											\
+#elif RV_MON_TYPE == RV_MON_PER_CPU
+
+/*
+ * per-cpu monitor variables
+ */
+static DEFINE_PER_CPU(struct da_monitor, RV_DA_MON_NAME);
+
+/*
+ * da_get_monitor - return current CPU monitor address
+ */
+static struct da_monitor *da_get_monitor(void)
+{
+	return this_cpu_ptr(&RV_DA_MON_NAME);
+}
+
+/*
+ * da_monitor_reset_all - reset all CPUs' monitor
+ */
+static void da_monitor_reset_all(void)
+{
+	struct da_monitor *da_mon;
+	int cpu;
+	for_each_cpu(cpu, cpu_online_mask) {
+		da_mon = per_cpu_ptr(&RV_DA_MON_NAME, cpu);
+		da_monitor_reset(da_mon);
+	}
+}
+
+/*
+ * da_monitor_init - initialize all CPUs' monitor
+ */
+static inline int da_monitor_init(void)
+{
+	da_monitor_reset_all();
+	return 0;
+}
+
+/*
+ * da_monitor_destroy - destroy the monitor
+ */
+static inline void da_monitor_destroy(void)
+{
+	return;
 }
 
 /*
  * Functions to define, init and get a per-task monitor.
  */
-#define DECLARE_DA_MON_INIT_PER_TASK(name, type)						\
-												\
-/*												\
- * The per-task monitor is stored a vector in the task struct. This variable			\
- * stores the position on the vector reserved for this monitor.					\
- */												\
-static int task_mon_slot_##name = RV_PER_TASK_MONITOR_INIT;					\
-												\
-/*												\
- * da_get_monitor_##name - return the monitor in the allocated slot for tsk 			\
- */												\
-static inline struct da_monitor *da_get_monitor_##name(struct task_struct *tsk)			\
-{												\
-	return &tsk->rv[task_mon_slot_##name].da_mon;						\
-}												\
-												\
-static void da_monitor_reset_all_##name(void)							\
-{												\
-	struct task_struct *g, *p;								\
-	int cpu;										\
-												\
-	read_lock(&tasklist_lock);								\
-	for_each_process_thread(g, p)								\
-		da_monitor_reset_##name(da_get_monitor_##name(p));				\
-	for_each_present_cpu(cpu)								\
-		da_monitor_reset_##name(da_get_monitor_##name(idle_task(cpu)));			\
-	read_unlock(&tasklist_lock);								\
-}												\
-												\
-/*												\
- * da_monitor_init_##name - initialize the per-task monitor					\
- *												\
- * Try to allocate a slot in the task's vector of monitors. If there				\
- * is an available slot, use it and reset all task's monitor.					\
- */												\
-static int da_monitor_init_##name(void)								\
-{												\
-	int slot;										\
-												\
-	slot = rv_get_task_monitor_slot();							\
-	if (slot < 0 || slot >= RV_PER_TASK_MONITOR_INIT)					\
-		return slot;									\
-												\
-	task_mon_slot_##name = slot;								\
-												\
-	da_monitor_reset_all_##name();								\
-	return 0;										\
-}												\
-												\
-/*												\
- * da_monitor_destroy_##name - return the allocated slot					\
- */												\
-static inline void da_monitor_destroy_##name(void)						\
-{												\
-	if (task_mon_slot_##name == RV_PER_TASK_MONITOR_INIT) {					\
-		WARN_ONCE(1, "Disabling a disabled monitor: " #name);				\
-		return;										\
-	}											\
-	rv_put_task_monitor_slot(task_mon_slot_##name);						\
-	task_mon_slot_##name = RV_PER_TASK_MONITOR_INIT;					\
-	return;											\
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+
+/*
+ * The per-task monitor is stored a vector in the task struct. This variable
+ * stores the position on the vector reserved for this monitor.
+ */
+static int task_mon_slot = RV_PER_TASK_MONITOR_INIT;
+
+/*
+ * da_get_monitor - return the monitor in the allocated slot for tsk
+ */
+static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
+{
+	return &tsk->rv[task_mon_slot].da_mon;
+}
+
+static void da_monitor_reset_all(void)
+{
+	struct task_struct *g, *p;
+	int cpu;
+
+	read_lock(&tasklist_lock);
+	for_each_process_thread(g, p)
+		da_monitor_reset(da_get_monitor(p));
+	for_each_present_cpu(cpu)
+		da_monitor_reset(da_get_monitor(idle_task(cpu)));
+	read_unlock(&tasklist_lock);
 }
 
 /*
- * Handle event for implicit monitor: da_get_monitor_##name() will figure out
+ * da_monitor_init - initialize the per-task monitor
+ *
+ * Try to allocate a slot in the task's vector of monitors. If there
+ * is an available slot, use it and reset all task's monitor.
+ */
+static int da_monitor_init(void)
+{
+	int slot;
+
+	slot = rv_get_task_monitor_slot();
+	if (slot < 0 || slot >= RV_PER_TASK_MONITOR_INIT)
+		return slot;
+
+	task_mon_slot = slot;
+
+	da_monitor_reset_all();
+	return 0;
+}
+
+/*
+ * da_monitor_destroy - return the allocated slot
+ */
+static inline void da_monitor_destroy(void)
+{
+	if (task_mon_slot == RV_PER_TASK_MONITOR_INIT) {
+		WARN_ONCE(1, "Disabling a disabled monitor: " __stringify(MONITOR_NAME));
+		return;
+	}
+	rv_put_task_monitor_slot(task_mon_slot);
+	task_mon_slot = RV_PER_TASK_MONITOR_INIT;
+	return;
+}
+#endif
+
+/*
+ * Handle event for implicit monitor: da_get_monitor() will figure out
  * the monitor.
  */
-#define DECLARE_DA_MON_MONITOR_HANDLER_IMPLICIT(name, type)					\
-												\
-static inline void __da_handle_event_##name(struct da_monitor *da_mon,				\
-					    enum events_##name event)				\
-{												\
-	bool retval;										\
-												\
-	retval = da_event_##name(da_mon, event);						\
-	if (!retval)										\
-		da_monitor_reset_##name(da_mon);						\
-}												\
-												\
-/*												\
- * da_handle_event_##name - handle an event							\
- */												\
-static inline void da_handle_event_##name(enum events_##name event)				\
-{												\
-	struct da_monitor *da_mon = da_get_monitor_##name();					\
-	bool retval;										\
-												\
-	retval = da_monitor_handling_event_##name(da_mon);					\
-	if (!retval)										\
-		return;										\
-												\
-	__da_handle_event_##name(da_mon, event);						\
-}												\
-												\
-/*												\
- * da_handle_start_event_##name - start monitoring or handle event				\
- *												\
- * This function is used to notify the monitor that the system is returning			\
- * to the initial state, so the monitor can start monitoring in the next event.			\
- * Thus:											\
- *												\
- * If the monitor already started, handle the event.						\
- * If the monitor did not start yet, start the monitor but skip the event.			\
- */												\
-static inline bool da_handle_start_event_##name(enum events_##name event)			\
-{												\
-	struct da_monitor *da_mon;								\
-												\
-	if (!da_monitor_enabled_##name())							\
-		return 0;									\
-												\
-	da_mon = da_get_monitor_##name();							\
-												\
-	if (unlikely(!da_monitoring_##name(da_mon))) {						\
-		da_monitor_start_##name(da_mon);						\
-		return 0;									\
-	}											\
-												\
-	__da_handle_event_##name(da_mon, event);						\
-												\
-	return 1;										\
-}												\
-												\
-/*												\
- * da_handle_start_run_event_##name - start monitoring and handle event				\
- *												\
- * This function is used to notify the monitor that the system is in the			\
- * initial state, so the monitor can start monitoring and handling event.			\
- */												\
-static inline bool da_handle_start_run_event_##name(enum events_##name event)			\
-{												\
-	struct da_monitor *da_mon;								\
-												\
-	if (!da_monitor_enabled_##name())							\
-		return 0;									\
-												\
-	da_mon = da_get_monitor_##name();							\
-												\
-	if (unlikely(!da_monitoring_##name(da_mon)))						\
-		da_monitor_start_##name(da_mon);						\
-												\
-	__da_handle_event_##name(da_mon, event);						\
-												\
-	return 1;										\
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+
+static inline void __da_handle_event(struct da_monitor *da_mon,
+					    enum events event)
+{
+	bool retval;
+
+	retval = da_event(da_mon, event);
+	if (!retval)
+		da_monitor_reset(da_mon);
+}
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event(enum events event)
+{
+	struct da_monitor *da_mon = da_get_monitor();
+	bool retval;
+
+	retval = da_monitor_handling_event(da_mon);
+	if (!retval)
+		return;
+
+	__da_handle_event(da_mon, event);
+}
+
+/*
+ * da_handle_start_event - start monitoring or handle event
+ *
+ * This function is used to notify the monitor that the system is returning
+ * to the initial state, so the monitor can start monitoring in the next event.
+ * Thus:
+ *
+ * If the monitor already started, handle the event.
+ * If the monitor did not start yet, start the monitor but skip the event.
+ */
+static inline bool da_handle_start_event(enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+
+	da_mon = da_get_monitor();
+
+	if (unlikely(!da_monitoring(da_mon))) {
+		da_monitor_start(da_mon);
+		return 0;
+	}
+
+	__da_handle_event(da_mon, event);
+
+	return 1;
+}
+
+/*
+ * da_handle_start_run_event - start monitoring and handle event
+ *
+ * This function is used to notify the monitor that the system is in the
+ * initial state, so the monitor can start monitoring and handling event.
+ */
+static inline bool da_handle_start_run_event(enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+
+	da_mon = da_get_monitor();
+
+	if (unlikely(!da_monitoring(da_mon)))
+		da_monitor_start(da_mon);
+
+	__da_handle_event(da_mon, event);
+
+	return 1;
 }
 
 /*
  * Handle event for per task.
  */
-#define DECLARE_DA_MON_MONITOR_HANDLER_PER_TASK(name, type)					\
-												\
-static inline void										\
-__da_handle_event_##name(struct da_monitor *da_mon, struct task_struct *tsk,			\
-			 enum events_##name event)						\
-{												\
-	bool retval;										\
-												\
-	retval = da_event_##name(da_mon, tsk, event);						\
-	if (!retval)										\
-		da_monitor_reset_##name(da_mon);						\
-}												\
-												\
-/*												\
- * da_handle_event_##name - handle an event							\
- */												\
-static inline void										\
-da_handle_event_##name(struct task_struct *tsk, enum events_##name event)			\
-{												\
-	struct da_monitor *da_mon = da_get_monitor_##name(tsk);					\
-	bool retval;										\
-												\
-	retval = da_monitor_handling_event_##name(da_mon);					\
-	if (!retval)										\
-		return;										\
-												\
-	__da_handle_event_##name(da_mon, tsk, event);						\
-}												\
-												\
-/*												\
- * da_handle_start_event_##name - start monitoring or handle event				\
- *												\
- * This function is used to notify the monitor that the system is returning			\
- * to the initial state, so the monitor can start monitoring in the next event.			\
- * Thus:											\
- *												\
- * If the monitor already started, handle the event.						\
- * If the monitor did not start yet, start the monitor but skip the event.			\
- */												\
-static inline bool										\
-da_handle_start_event_##name(struct task_struct *tsk, enum events_##name event)			\
-{												\
-	struct da_monitor *da_mon;								\
-												\
-	if (!da_monitor_enabled_##name())							\
-		return 0;									\
-												\
-	da_mon = da_get_monitor_##name(tsk);							\
-												\
-	if (unlikely(!da_monitoring_##name(da_mon))) {						\
-		da_monitor_start_##name(da_mon);						\
-		return 0;									\
-	}											\
-												\
-	__da_handle_event_##name(da_mon, tsk, event);						\
-												\
-	return 1;										\
-}												\
-												\
-/*												\
- * da_handle_start_run_event_##name - start monitoring and handle event				\
- *												\
- * This function is used to notify the monitor that the system is in the			\
- * initial state, so the monitor can start monitoring and handling event.			\
- */												\
-static inline bool										\
-da_handle_start_run_event_##name(struct task_struct *tsk, enum events_##name event)		\
-{												\
-	struct da_monitor *da_mon;								\
-												\
-	if (!da_monitor_enabled_##name())							\
-		return 0;									\
-												\
-	da_mon = da_get_monitor_##name(tsk);							\
-												\
-	if (unlikely(!da_monitoring_##name(da_mon)))						\
-		da_monitor_start_##name(da_mon);						\
-												\
-	__da_handle_event_##name(da_mon, tsk, event);						\
-												\
-	return 1;										\
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+
+static inline void
+__da_handle_event(struct da_monitor *da_mon, struct task_struct *tsk,
+			 enum events event)
+{
+	bool retval;
+
+	retval = da_event(da_mon, tsk, event);
+	if (!retval)
+		da_monitor_reset(da_mon);
+}
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void
+da_handle_event(struct task_struct *tsk, enum events event)
+{
+	struct da_monitor *da_mon = da_get_monitor(tsk);
+	bool retval;
+
+	retval = da_monitor_handling_event(da_mon);
+	if (!retval)
+		return;
+
+	__da_handle_event(da_mon, tsk, event);
+}
+
+/*
+ * da_handle_start_event - start monitoring or handle event
+ *
+ * This function is used to notify the monitor that the system is returning
+ * to the initial state, so the monitor can start monitoring in the next event.
+ * Thus:
+ *
+ * If the monitor already started, handle the event.
+ * If the monitor did not start yet, start the monitor but skip the event.
+ */
+static inline bool
+da_handle_start_event(struct task_struct *tsk, enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+
+	da_mon = da_get_monitor(tsk);
+
+	if (unlikely(!da_monitoring(da_mon))) {
+		da_monitor_start(da_mon);
+		return 0;
+	}
+
+	__da_handle_event(da_mon, tsk, event);
+
+	return 1;
 }
 
+/*
+ * da_handle_start_run_event - start monitoring and handle event
+ *
+ * This function is used to notify the monitor that the system is in the
+ * initial state, so the monitor can start monitoring and handling event.
+ */
+static inline bool
+da_handle_start_run_event(struct task_struct *tsk, enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+
+	da_mon = da_get_monitor(tsk);
+
+	if (unlikely(!da_monitoring(da_mon)))
+		da_monitor_start(da_mon);
+
+	__da_handle_event(da_mon, tsk, event);
+
+	return 1;
+}
+#endif
+
 /*
  * Entry point for the global monitor.
  */
-#define DECLARE_DA_MON_GLOBAL(name, type)							\
-												\
-DECLARE_AUTOMATA_HELPERS(name, type)								\
-DECLARE_DA_MON_GENERIC_HELPERS(name, type)							\
-DECLARE_DA_MON_MODEL_HANDLER_IMPLICIT(name, type)						\
-DECLARE_DA_MON_INIT_GLOBAL(name, type)								\
-DECLARE_DA_MON_MONITOR_HANDLER_IMPLICIT(name, type)
+#define DECLARE_DA_MON_GLOBAL(name, type)
 
 /*
  * Entry point for the per-cpu monitor.
  */
-#define DECLARE_DA_MON_PER_CPU(name, type)							\
-												\
-DECLARE_AUTOMATA_HELPERS(name, type)								\
-DECLARE_DA_MON_GENERIC_HELPERS(name, type)							\
-DECLARE_DA_MON_MODEL_HANDLER_IMPLICIT(name, type)						\
-DECLARE_DA_MON_INIT_PER_CPU(name, type)								\
-DECLARE_DA_MON_MONITOR_HANDLER_IMPLICIT(name, type)
+#define DECLARE_DA_MON_PER_CPU(name, type)
 
 /*
  * Entry point for the per-task monitor.
  */
-#define DECLARE_DA_MON_PER_TASK(name, type)							\
-												\
-DECLARE_AUTOMATA_HELPERS(name, type)								\
-DECLARE_DA_MON_GENERIC_HELPERS(name, type)							\
-DECLARE_DA_MON_MODEL_HANDLER_PER_TASK(name, type)						\
-DECLARE_DA_MON_INIT_PER_TASK(name, type)							\
-DECLARE_DA_MON_MONITOR_HANDLER_PER_TASK(name, type)
+#define DECLARE_DA_MON_PER_TASK(name, type)
diff --git a/kernel/trace/rv/monitors/nrp/nrp.c b/kernel/trace/rv/monitors/nrp/nrp.c
index 5a83b7171432..4b5646a70094 100644
--- a/kernel/trace/rv/monitors/nrp/nrp.c
+++ b/kernel/trace/rv/monitors/nrp/nrp.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "nrp"
 
@@ -15,17 +14,16 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_TASK
 #include "nrp.h"
-
-static struct rv_monitor rv_nrp;
-DECLARE_DA_MON_PER_TASK(nrp, unsigned char);
+#include <rv/da_monitor.h>
 
 #ifdef CONFIG_X86_LOCAL_APIC
 #include <asm/trace/irq_vectors.h>
 
 static void handle_vector_irq_entry(void *data, int vector)
 {
-	da_handle_event_nrp(current, irq_entry_nrp);
+	da_handle_event(current, irq_entry_nrp);
 }
 
 static void attach_vector_irq(void)
@@ -60,7 +58,7 @@ static void detach_vector_irq(void) { }
 
 static void handle_irq_entry(void *data, int irq, struct irqaction *action)
 {
-	da_handle_event_nrp(current, irq_entry_nrp);
+	da_handle_event(current, irq_entry_nrp);
 }
 
 static void handle_sched_need_resched(void *data, struct task_struct *tsk,
@@ -72,22 +70,22 @@ static void handle_sched_need_resched(void *data, struct task_struct *tsk,
 	 * which may not mirror the system state but makes the monitor simpler,
 	 */
 	if (tif == TIF_NEED_RESCHED)
-		da_handle_start_event_nrp(tsk, sched_need_resched_nrp);
+		da_handle_start_event(tsk, sched_need_resched_nrp);
 }
 
 static void handle_schedule_entry(void *data, bool preempt)
 {
 	if (preempt)
-		da_handle_event_nrp(current, schedule_entry_preempt_nrp);
+		da_handle_event(current, schedule_entry_preempt_nrp);
 	else
-		da_handle_event_nrp(current, schedule_entry_nrp);
+		da_handle_event(current, schedule_entry_nrp);
 }
 
 static int enable_nrp(void)
 {
 	int retval;
 
-	retval = da_monitor_init_nrp();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -101,33 +99,33 @@ static int enable_nrp(void)
 
 static void disable_nrp(void)
 {
-	rv_nrp.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("nrp", irq_handler_entry, handle_irq_entry);
 	rv_detach_trace_probe("nrp", sched_set_need_resched_tp, handle_sched_need_resched);
 	rv_detach_trace_probe("nrp", sched_entry_tp, handle_schedule_entry);
 	detach_vector_irq();
 
-	da_monitor_destroy_nrp();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_nrp = {
+static struct rv_monitor rv_this = {
 	.name = "nrp",
 	.description = "need resched preempts.",
 	.enable = enable_nrp,
 	.disable = disable_nrp,
-	.reset = da_monitor_reset_all_nrp,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_nrp(void)
 {
-	return rv_register_monitor(&rv_nrp, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_nrp(void)
 {
-	rv_unregister_monitor(&rv_nrp);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_nrp);
diff --git a/kernel/trace/rv/monitors/nrp/nrp.h b/kernel/trace/rv/monitors/nrp/nrp.h
index c9f12207cbf6..c2ec83da2124 100644
--- a/kernel/trace/rv/monitors/nrp/nrp.h
+++ b/kernel/trace/rv/monitors/nrp/nrp.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME nrp
+
 enum states_nrp {
 	preempt_irq_nrp = 0,
 	any_thread_running_nrp,
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 50d64e7fb8c4..25a40e90fa40 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "opid"
 
@@ -16,17 +15,16 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "opid.h"
-
-static struct rv_monitor rv_opid;
-DECLARE_DA_MON_PER_CPU(opid, unsigned char);
+#include <rv/da_monitor.h>
 
 #ifdef CONFIG_X86_LOCAL_APIC
 #include <asm/trace/irq_vectors.h>
 
 static void handle_vector_irq_entry(void *data, int vector)
 {
-	da_handle_event_opid(irq_entry_opid);
+	da_handle_event(irq_entry_opid);
 }
 
 static void attach_vector_irq(void)
@@ -61,52 +59,52 @@ static void detach_vector_irq(void) { }
 
 static void handle_irq_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_opid(irq_disable_opid);
+	da_handle_event(irq_disable_opid);
 }
 
 static void handle_irq_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_opid(irq_enable_opid);
+	da_handle_event(irq_enable_opid);
 }
 
 static void handle_irq_entry(void *data, int irq, struct irqaction *action)
 {
-	da_handle_event_opid(irq_entry_opid);
+	da_handle_event(irq_entry_opid);
 }
 
 static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_opid(preempt_disable_opid);
+	da_handle_event(preempt_disable_opid);
 }
 
 static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_opid(preempt_enable_opid);
+	da_handle_event(preempt_enable_opid);
 }
 
 static void handle_sched_need_resched(void *data, struct task_struct *tsk, int cpu, int tif)
 {
 	/* The monitor's intitial state is not in_irq */
 	if (this_cpu_read(hardirq_context))
-		da_handle_event_opid(sched_need_resched_opid);
+		da_handle_event(sched_need_resched_opid);
 	else
-		da_handle_start_event_opid(sched_need_resched_opid);
+		da_handle_start_event(sched_need_resched_opid);
 }
 
 static void handle_sched_waking(void *data, struct task_struct *p)
 {
 	/* The monitor's intitial state is not in_irq */
 	if (this_cpu_read(hardirq_context))
-		da_handle_event_opid(sched_waking_opid);
+		da_handle_event(sched_waking_opid);
 	else
-		da_handle_start_event_opid(sched_waking_opid);
+		da_handle_start_event(sched_waking_opid);
 }
 
 static int enable_opid(void)
 {
 	int retval;
 
-	retval = da_monitor_init_opid();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -124,7 +122,7 @@ static int enable_opid(void)
 
 static void disable_opid(void)
 {
-	rv_opid.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("opid", irq_disable, handle_irq_disable);
 	rv_detach_trace_probe("opid", irq_enable, handle_irq_enable);
@@ -135,29 +133,29 @@ static void disable_opid(void)
 	rv_detach_trace_probe("opid", sched_waking, handle_sched_waking);
 	detach_vector_irq();
 
-	da_monitor_destroy_opid();
+	da_monitor_destroy();
 }
 
 /*
  * This is the monitor register section.
  */
-static struct rv_monitor rv_opid = {
+static struct rv_monitor rv_this = {
 	.name = "opid",
 	.description = "operations with preemption and irq disabled.",
 	.enable = enable_opid,
 	.disable = disable_opid,
-	.reset = da_monitor_reset_all_opid,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_opid(void)
 {
-	return rv_register_monitor(&rv_opid, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_opid(void)
 {
-	rv_unregister_monitor(&rv_opid);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_opid);
diff --git a/kernel/trace/rv/monitors/opid/opid.h b/kernel/trace/rv/monitors/opid/opid.h
index b4b8c2ff7f64..5014f1b85ecf 100644
--- a/kernel/trace/rv/monitors/opid/opid.h
+++ b/kernel/trace/rv/monitors/opid/opid.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME opid
+
 enum states_opid {
 	disabled_opid = 0,
 	enabled_opid,
diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c
index 04c36405e2e3..5a3bd5e16e62 100644
--- a/kernel/trace/rv/monitors/sco/sco.c
+++ b/kernel/trace/rv/monitors/sco/sco.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "sco"
 
@@ -14,31 +13,30 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "sco.h"
-
-static struct rv_monitor rv_sco;
-DECLARE_DA_MON_PER_CPU(sco, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
 {
-	da_handle_start_event_sco(sched_set_state_sco);
+	da_handle_start_event(sched_set_state_sco);
 }
 
 static void handle_schedule_entry(void *data, bool preempt)
 {
-	da_handle_event_sco(schedule_entry_sco);
+	da_handle_event(schedule_entry_sco);
 }
 
 static void handle_schedule_exit(void *data, bool is_switch)
 {
-	da_handle_start_event_sco(schedule_exit_sco);
+	da_handle_start_event(schedule_exit_sco);
 }
 
 static int enable_sco(void)
 {
 	int retval;
 
-	retval = da_monitor_init_sco();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -51,32 +49,32 @@ static int enable_sco(void)
 
 static void disable_sco(void)
 {
-	rv_sco.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("sco", sched_set_state_tp, handle_sched_set_state);
 	rv_detach_trace_probe("sco", sched_entry_tp, handle_schedule_entry);
 	rv_detach_trace_probe("sco", sched_exit_tp, handle_schedule_exit);
 
-	da_monitor_destroy_sco();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_sco = {
+static struct rv_monitor rv_this = {
 	.name = "sco",
 	.description = "scheduling context operations.",
 	.enable = enable_sco,
 	.disable = disable_sco,
-	.reset = da_monitor_reset_all_sco,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_sco(void)
 {
-	return rv_register_monitor(&rv_sco, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_sco(void)
 {
-	rv_unregister_monitor(&rv_sco);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_sco);
diff --git a/kernel/trace/rv/monitors/sco/sco.h b/kernel/trace/rv/monitors/sco/sco.h
index 7a4c1f2d5ca1..06b1c420ce54 100644
--- a/kernel/trace/rv/monitors/sco/sco.h
+++ b/kernel/trace/rv/monitors/sco/sco.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME sco
+
 enum states_sco {
 	thread_context_sco = 0,
 	scheduling_context_sco,
diff --git a/kernel/trace/rv/monitors/scpd/scpd.c b/kernel/trace/rv/monitors/scpd/scpd.c
index 1e351ba52fee..83b48627dc9f 100644
--- a/kernel/trace/rv/monitors/scpd/scpd.c
+++ b/kernel/trace/rv/monitors/scpd/scpd.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "scpd"
 
@@ -15,36 +14,35 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "scpd.h"
-
-static struct rv_monitor rv_scpd;
-DECLARE_DA_MON_PER_CPU(scpd, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_scpd(preempt_disable_scpd);
+	da_handle_event(preempt_disable_scpd);
 }
 
 static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_start_event_scpd(preempt_enable_scpd);
+	da_handle_start_event(preempt_enable_scpd);
 }
 
 static void handle_schedule_entry(void *data, bool preempt)
 {
-	da_handle_event_scpd(schedule_entry_scpd);
+	da_handle_event(schedule_entry_scpd);
 }
 
 static void handle_schedule_exit(void *data, bool is_switch)
 {
-	da_handle_event_scpd(schedule_exit_scpd);
+	da_handle_event(schedule_exit_scpd);
 }
 
 static int enable_scpd(void)
 {
 	int retval;
 
-	retval = da_monitor_init_scpd();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -58,33 +56,33 @@ static int enable_scpd(void)
 
 static void disable_scpd(void)
 {
-	rv_scpd.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("scpd", preempt_disable, handle_preempt_disable);
 	rv_detach_trace_probe("scpd", preempt_enable, handle_preempt_enable);
 	rv_detach_trace_probe("scpd", sched_entry_tp, handle_schedule_entry);
 	rv_detach_trace_probe("scpd", sched_exit_tp, handle_schedule_exit);
 
-	da_monitor_destroy_scpd();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_scpd = {
+static struct rv_monitor rv_this = {
 	.name = "scpd",
 	.description = "schedule called with preemption disabled.",
 	.enable = enable_scpd,
 	.disable = disable_scpd,
-	.reset = da_monitor_reset_all_scpd,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_scpd(void)
 {
-	return rv_register_monitor(&rv_scpd, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_scpd(void)
 {
-	rv_unregister_monitor(&rv_scpd);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_scpd);
diff --git a/kernel/trace/rv/monitors/scpd/scpd.h b/kernel/trace/rv/monitors/scpd/scpd.h
index 295f735a5811..4a725a68085a 100644
--- a/kernel/trace/rv/monitors/scpd/scpd.h
+++ b/kernel/trace/rv/monitors/scpd/scpd.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME scpd
+
 enum states_scpd {
 	cant_sched_scpd = 0,
 	can_sched_scpd,
diff --git a/kernel/trace/rv/monitors/snep/snep.c b/kernel/trace/rv/monitors/snep/snep.c
index 558950f524a5..b80b73795dec 100644
--- a/kernel/trace/rv/monitors/snep/snep.c
+++ b/kernel/trace/rv/monitors/snep/snep.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "snep"
 
@@ -15,36 +14,35 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "snep.h"
-
-static struct rv_monitor rv_snep;
-DECLARE_DA_MON_PER_CPU(snep, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_start_event_snep(preempt_disable_snep);
+	da_handle_start_event(preempt_disable_snep);
 }
 
 static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_start_event_snep(preempt_enable_snep);
+	da_handle_start_event(preempt_enable_snep);
 }
 
 static void handle_schedule_entry(void *data, bool preempt)
 {
-	da_handle_event_snep(schedule_entry_snep);
+	da_handle_event(schedule_entry_snep);
 }
 
 static void handle_schedule_exit(void *data, bool is_switch)
 {
-	da_handle_start_event_snep(schedule_exit_snep);
+	da_handle_start_event(schedule_exit_snep);
 }
 
 static int enable_snep(void)
 {
 	int retval;
 
-	retval = da_monitor_init_snep();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -58,33 +56,33 @@ static int enable_snep(void)
 
 static void disable_snep(void)
 {
-	rv_snep.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("snep", preempt_disable, handle_preempt_disable);
 	rv_detach_trace_probe("snep", preempt_enable, handle_preempt_enable);
 	rv_detach_trace_probe("snep", sched_entry_tp, handle_schedule_entry);
 	rv_detach_trace_probe("snep", sched_exit_tp, handle_schedule_exit);
 
-	da_monitor_destroy_snep();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_snep = {
+static struct rv_monitor rv_this = {
 	.name = "snep",
 	.description = "schedule does not enable preempt.",
 	.enable = enable_snep,
 	.disable = disable_snep,
-	.reset = da_monitor_reset_all_snep,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_snep(void)
 {
-	return rv_register_monitor(&rv_snep, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_snep(void)
 {
-	rv_unregister_monitor(&rv_snep);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_snep);
diff --git a/kernel/trace/rv/monitors/snep/snep.h b/kernel/trace/rv/monitors/snep/snep.h
index 4cd9abb77b7b..753080dc5fa1 100644
--- a/kernel/trace/rv/monitors/snep/snep.h
+++ b/kernel/trace/rv/monitors/snep/snep.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME snep
+
 enum states_snep {
 	non_scheduling_context_snep = 0,
 	scheduling_contex_snep,
diff --git a/kernel/trace/rv/monitors/snroc/snroc.c b/kernel/trace/rv/monitors/snroc/snroc.c
index 540e686e699f..f168b1a4b12c 100644
--- a/kernel/trace/rv/monitors/snroc/snroc.c
+++ b/kernel/trace/rv/monitors/snroc/snroc.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "snroc"
 
@@ -14,14 +13,13 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_TASK
 #include "snroc.h"
-
-static struct rv_monitor rv_snroc;
-DECLARE_DA_MON_PER_TASK(snroc, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
 {
-	da_handle_event_snroc(tsk, sched_set_state_snroc);
+	da_handle_event(tsk, sched_set_state_snroc);
 }
 
 static void handle_sched_switch(void *data, bool preempt,
@@ -29,15 +27,15 @@ static void handle_sched_switch(void *data, bool preempt,
 				struct task_struct *next,
 				unsigned int prev_state)
 {
-	da_handle_start_event_snroc(prev, sched_switch_out_snroc);
-	da_handle_event_snroc(next, sched_switch_in_snroc);
+	da_handle_start_event(prev, sched_switch_out_snroc);
+	da_handle_event(next, sched_switch_in_snroc);
 }
 
 static int enable_snroc(void)
 {
 	int retval;
 
-	retval = da_monitor_init_snroc();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -49,31 +47,31 @@ static int enable_snroc(void)
 
 static void disable_snroc(void)
 {
-	rv_snroc.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("snroc", sched_set_state_tp, handle_sched_set_state);
 	rv_detach_trace_probe("snroc", sched_switch, handle_sched_switch);
 
-	da_monitor_destroy_snroc();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_snroc = {
+static struct rv_monitor rv_this = {
 	.name = "snroc",
 	.description = "set non runnable on its own context.",
 	.enable = enable_snroc,
 	.disable = disable_snroc,
-	.reset = da_monitor_reset_all_snroc,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_snroc(void)
 {
-	return rv_register_monitor(&rv_snroc, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_snroc(void)
 {
-	rv_unregister_monitor(&rv_snroc);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_snroc);
diff --git a/kernel/trace/rv/monitors/snroc/snroc.h b/kernel/trace/rv/monitors/snroc/snroc.h
index c3650a2b1b10..ada5ee08bdab 100644
--- a/kernel/trace/rv/monitors/snroc/snroc.h
+++ b/kernel/trace/rv/monitors/snroc/snroc.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME snroc
+
 enum states_snroc {
 	other_context_snroc = 0,
 	own_context_snroc,
diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c
index 84b8d890d9d4..a91321c890cd 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.c
+++ b/kernel/trace/rv/monitors/sssw/sssw.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "sssw"
 
@@ -15,17 +14,16 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_TASK
 #include "sssw.h"
-
-static struct rv_monitor rv_sssw;
-DECLARE_DA_MON_PER_TASK(sssw, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
 {
 	if (state == TASK_RUNNING)
-		da_handle_start_event_sssw(tsk, sched_set_state_runnable_sssw);
+		da_handle_start_event(tsk, sched_set_state_runnable_sssw);
 	else
-		da_handle_event_sssw(tsk, sched_set_state_sleepable_sssw);
+		da_handle_event(tsk, sched_set_state_sleepable_sssw);
 }
 
 static void handle_sched_switch(void *data, bool preempt,
@@ -34,15 +32,15 @@ static void handle_sched_switch(void *data, bool preempt,
 				unsigned int prev_state)
 {
 	if (preempt)
-		da_handle_event_sssw(prev, sched_switch_preempt_sssw);
+		da_handle_event(prev, sched_switch_preempt_sssw);
 	else if (prev_state == TASK_RUNNING)
-		da_handle_event_sssw(prev, sched_switch_yield_sssw);
+		da_handle_event(prev, sched_switch_yield_sssw);
 	else if (prev_state == TASK_RTLOCK_WAIT)
 		/* special case of sleeping task with racy conditions */
-		da_handle_event_sssw(prev, sched_switch_blocking_sssw);
+		da_handle_event(prev, sched_switch_blocking_sssw);
 	else
-		da_handle_event_sssw(prev, sched_switch_suspend_sssw);
-	da_handle_event_sssw(next, sched_switch_in_sssw);
+		da_handle_event(prev, sched_switch_suspend_sssw);
+	da_handle_event(next, sched_switch_in_sssw);
 }
 
 static void handle_sched_wakeup(void *data, struct task_struct *p)
@@ -51,21 +49,21 @@ static void handle_sched_wakeup(void *data, struct task_struct *p)
 	 * Wakeup can also lead to signal_wakeup although the system is
 	 * actually runnable. The monitor can safely start with this event.
 	 */
-	da_handle_start_event_sssw(p, sched_wakeup_sssw);
+	da_handle_start_event(p, sched_wakeup_sssw);
 }
 
 static void handle_signal_deliver(void *data, int sig,
 				   struct kernel_siginfo *info,
 				   struct k_sigaction *ka)
 {
-	da_handle_event_sssw(current, signal_deliver_sssw);
+	da_handle_event(current, signal_deliver_sssw);
 }
 
 static int enable_sssw(void)
 {
 	int retval;
 
-	retval = da_monitor_init_sssw();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -79,33 +77,33 @@ static int enable_sssw(void)
 
 static void disable_sssw(void)
 {
-	rv_sssw.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("sssw", sched_set_state_tp, handle_sched_set_state);
 	rv_detach_trace_probe("sssw", sched_switch, handle_sched_switch);
 	rv_detach_trace_probe("sssw", sched_wakeup, handle_sched_wakeup);
 	rv_detach_trace_probe("sssw", signal_deliver, handle_signal_deliver);
 
-	da_monitor_destroy_sssw();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_sssw = {
+static struct rv_monitor rv_this = {
 	.name = "sssw",
 	.description = "set state sleep and wakeup.",
 	.enable = enable_sssw,
 	.disable = disable_sssw,
-	.reset = da_monitor_reset_all_sssw,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_sssw(void)
 {
-	return rv_register_monitor(&rv_sssw, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_sssw(void)
 {
-	rv_unregister_monitor(&rv_sssw);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_sssw);
diff --git a/kernel/trace/rv/monitors/sssw/sssw.h b/kernel/trace/rv/monitors/sssw/sssw.h
index 243d54050c94..8409eaadc7e0 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.h
+++ b/kernel/trace/rv/monitors/sssw/sssw.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME sssw
+
 enum states_sssw {
 	runnable_sssw = 0,
 	signal_wakeup_sssw,
diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c
index c4a9cd67c1d2..ce031cbf202a 100644
--- a/kernel/trace/rv/monitors/sts/sts.c
+++ b/kernel/trace/rv/monitors/sts/sts.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "sts"
 
@@ -16,17 +15,16 @@
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "sts.h"
-
-static struct rv_monitor rv_sts;
-DECLARE_DA_MON_PER_CPU(sts, unsigned char);
+#include <rv/da_monitor.h>
 
 #ifdef CONFIG_X86_LOCAL_APIC
 #include <asm/trace/irq_vectors.h>
 
 static void handle_vector_irq_entry(void *data, int vector)
 {
-	da_handle_event_sts(irq_entry_sts);
+	da_handle_event(irq_entry_sts);
 }
 
 static void attach_vector_irq(void)
@@ -61,17 +59,17 @@ static void detach_vector_irq(void) { }
 
 static void handle_irq_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_sts(irq_disable_sts);
+	da_handle_event(irq_disable_sts);
 }
 
 static void handle_irq_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_sts(irq_enable_sts);
+	da_handle_event(irq_enable_sts);
 }
 
 static void handle_irq_entry(void *data, int irq, struct irqaction *action)
 {
-	da_handle_event_sts(irq_entry_sts);
+	da_handle_event(irq_entry_sts);
 }
 
 static void handle_sched_switch(void *data, bool preempt,
@@ -79,24 +77,24 @@ static void handle_sched_switch(void *data, bool preempt,
 				struct task_struct *next,
 				unsigned int prev_state)
 {
-	da_handle_event_sts(sched_switch_sts);
+	da_handle_event(sched_switch_sts);
 }
 
 static void handle_schedule_entry(void *data, bool preempt)
 {
-	da_handle_event_sts(schedule_entry_sts);
+	da_handle_event(schedule_entry_sts);
 }
 
 static void handle_schedule_exit(void *data, bool is_switch)
 {
-	da_handle_start_event_sts(schedule_exit_sts);
+	da_handle_start_event(schedule_exit_sts);
 }
 
 static int enable_sts(void)
 {
 	int retval;
 
-	retval = da_monitor_init_sts();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -113,7 +111,7 @@ static int enable_sts(void)
 
 static void disable_sts(void)
 {
-	rv_sts.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("sts", irq_disable, handle_irq_disable);
 	rv_detach_trace_probe("sts", irq_enable, handle_irq_enable);
@@ -123,29 +121,29 @@ static void disable_sts(void)
 	rv_detach_trace_probe("sts", sched_exit_tp, handle_schedule_exit);
 	detach_vector_irq();
 
-	da_monitor_destroy_sts();
+	da_monitor_destroy();
 }
 
 /*
  * This is the monitor register section.
  */
-static struct rv_monitor rv_sts = {
+static struct rv_monitor rv_this = {
 	.name = "sts",
 	.description = "schedule implies task switch.",
 	.enable = enable_sts,
 	.disable = disable_sts,
-	.reset = da_monitor_reset_all_sts,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_sts(void)
 {
-	return rv_register_monitor(&rv_sts, &rv_sched);
+	return rv_register_monitor(&rv_this, &rv_sched);
 }
 
 static void __exit unregister_sts(void)
 {
-	rv_unregister_monitor(&rv_sts);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_sts);
diff --git a/kernel/trace/rv/monitors/sts/sts.h b/kernel/trace/rv/monitors/sts/sts.h
index 3368b6599a00..3779d7f99404 100644
--- a/kernel/trace/rv/monitors/sts/sts.h
+++ b/kernel/trace/rv/monitors/sts/sts.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME sts
+
 enum states_sts {
 	can_sched_sts = 0,
 	cant_sched_sts,
diff --git a/kernel/trace/rv/monitors/wip/wip.c b/kernel/trace/rv/monitors/wip/wip.c
index 4b4e99615a11..22d77ec42463 100644
--- a/kernel/trace/rv/monitors/wip/wip.c
+++ b/kernel/trace/rv/monitors/wip/wip.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "wip"
 
@@ -14,31 +13,30 @@
 #include <trace/events/sched.h>
 #include <trace/events/preemptirq.h>
 
+#define RV_MON_TYPE RV_MON_PER_CPU
 #include "wip.h"
-
-static struct rv_monitor rv_wip;
-DECLARE_DA_MON_PER_CPU(wip, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_event_wip(preempt_disable_wip);
+	da_handle_event(preempt_disable_wip);
 }
 
 static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
 {
-	da_handle_start_event_wip(preempt_enable_wip);
+	da_handle_start_event(preempt_enable_wip);
 }
 
 static void handle_sched_waking(void *data, struct task_struct *task)
 {
-	da_handle_event_wip(sched_waking_wip);
+	da_handle_event(sched_waking_wip);
 }
 
 static int enable_wip(void)
 {
 	int retval;
 
-	retval = da_monitor_init_wip();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -51,32 +49,32 @@ static int enable_wip(void)
 
 static void disable_wip(void)
 {
-	rv_wip.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("wip", preempt_disable, handle_preempt_disable);
 	rv_detach_trace_probe("wip", preempt_enable, handle_preempt_enable);
 	rv_detach_trace_probe("wip", sched_waking, handle_sched_waking);
 
-	da_monitor_destroy_wip();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_wip = {
+static struct rv_monitor rv_this = {
 	.name = "wip",
 	.description = "wakeup in preemptive per-cpu testing monitor.",
 	.enable = enable_wip,
 	.disable = disable_wip,
-	.reset = da_monitor_reset_all_wip,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_wip(void)
 {
-	return rv_register_monitor(&rv_wip, NULL);
+	return rv_register_monitor(&rv_this, NULL);
 }
 
 static void __exit unregister_wip(void)
 {
-	rv_unregister_monitor(&rv_wip);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_wip);
diff --git a/kernel/trace/rv/monitors/wip/wip.h b/kernel/trace/rv/monitors/wip/wip.h
index c7193748bf36..cfdc52975354 100644
--- a/kernel/trace/rv/monitors/wip/wip.h
+++ b/kernel/trace/rv/monitors/wip/wip.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME wip
+
 enum states_wip {
 	preemptive_wip = 0,
 	non_preemptive_wip,
diff --git a/kernel/trace/rv/monitors/wwnr/wwnr.c b/kernel/trace/rv/monitors/wwnr/wwnr.c
index 4145bea2729e..579e7e217ee0 100644
--- a/kernel/trace/rv/monitors/wwnr/wwnr.c
+++ b/kernel/trace/rv/monitors/wwnr/wwnr.c
@@ -6,40 +6,38 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "wwnr"
 
 #include <rv_trace.h>
 #include <trace/events/sched.h>
 
+#define RV_MON_TYPE RV_MON_PER_TASK
 #include "wwnr.h"
-
-static struct rv_monitor rv_wwnr;
-DECLARE_DA_MON_PER_TASK(wwnr, unsigned char);
+#include <rv/da_monitor.h>
 
 static void handle_switch(void *data, bool preempt, struct task_struct *p,
 			  struct task_struct *n, unsigned int prev_state)
 {
 	/* start monitoring only after the first suspension */
 	if (prev_state == TASK_INTERRUPTIBLE)
-		da_handle_start_event_wwnr(p, switch_out_wwnr);
+		da_handle_start_event(p, switch_out_wwnr);
 	else
-		da_handle_event_wwnr(p, switch_out_wwnr);
+		da_handle_event(p, switch_out_wwnr);
 
-	da_handle_event_wwnr(n, switch_in_wwnr);
+	da_handle_event(n, switch_in_wwnr);
 }
 
 static void handle_wakeup(void *data, struct task_struct *p)
 {
-	da_handle_event_wwnr(p, wakeup_wwnr);
+	da_handle_event(p, wakeup_wwnr);
 }
 
 static int enable_wwnr(void)
 {
 	int retval;
 
-	retval = da_monitor_init_wwnr();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -51,31 +49,31 @@ static int enable_wwnr(void)
 
 static void disable_wwnr(void)
 {
-	rv_wwnr.enabled = 0;
+	rv_this.enabled = 0;
 
 	rv_detach_trace_probe("wwnr", sched_switch, handle_switch);
 	rv_detach_trace_probe("wwnr", sched_wakeup, handle_wakeup);
 
-	da_monitor_destroy_wwnr();
+	da_monitor_destroy();
 }
 
-static struct rv_monitor rv_wwnr = {
+static struct rv_monitor rv_this = {
 	.name = "wwnr",
 	.description = "wakeup while not running per-task testing model.",
 	.enable = enable_wwnr,
 	.disable = disable_wwnr,
-	.reset = da_monitor_reset_all_wwnr,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 static int __init register_wwnr(void)
 {
-	return rv_register_monitor(&rv_wwnr, NULL);
+	return rv_register_monitor(&rv_this, NULL);
 }
 
 static void __exit unregister_wwnr(void)
 {
-	rv_unregister_monitor(&rv_wwnr);
+	rv_unregister_monitor(&rv_this);
 }
 
 module_init(register_wwnr);
diff --git a/kernel/trace/rv/monitors/wwnr/wwnr.h b/kernel/trace/rv/monitors/wwnr/wwnr.h
index 0a59d23edf61..85d12e42a955 100644
--- a/kernel/trace/rv/monitors/wwnr/wwnr.h
+++ b/kernel/trace/rv/monitors/wwnr/wwnr.h
@@ -5,6 +5,8 @@
  *   Documentation/trace/rv/deterministic_automata.rst
  */
 
+#define MONITOR_NAME wwnr
+
 enum states_wwnr {
 	not_running_wwnr = 0,
 	running_wwnr,
-- 
2.51.0


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

* [PATCH v2 02/20] rv: Cleanup da_monitor after refactor
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  8:49   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types Gabriele Monaco
                   ` (18 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Previous changes refactored the da_monitor header file to avoid using
macros, however empty macros (e.g. DECLARE_DA_FUNCTION) were left to
ease review with diff tools.
Most macros also get the argument type which doesn't really have a
purpose since states have their own enum and the storage in struct
da_monitor is fixed to unsigned int.

Remove empty and no longer required macros and substitute the type
parameter with the appropriate enum.
Additionally break long line and adjust the format overall.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/rv/automata.h   |  24 +++-----
 include/rv/da_monitor.h | 121 +++++++++++++++-------------------------
 2 files changed, 53 insertions(+), 92 deletions(-)

diff --git a/include/rv/automata.h b/include/rv/automata.h
index 5b5d2e94c034..4a4eb40cf09a 100644
--- a/include/rv/automata.h
+++ b/include/rv/automata.h
@@ -6,29 +6,19 @@
  * models in C generated by the dot2k tool.
  */
 
+#ifndef _RV_AUTOMATA_H
+#define _RV_AUTOMATA_H
+
 #ifndef MONITOR_NAME
 #error "MONITOR_NAME macro is not defined. Did you include $(MODEL_NAME).h generated by rvgen?"
 #endif
 
-#ifndef type
-#define type unsigned char
-#endif
-
 #define RV_AUTOMATON_NAME CONCATENATE(automaton_, MONITOR_NAME)
 #define EVENT_MAX CONCATENATE(event_max_, MONITOR_NAME)
 #define STATE_MAX CONCATENATE(state_max_, MONITOR_NAME)
 #define events CONCATENATE(events_, MONITOR_NAME)
 #define states CONCATENATE(states_, MONITOR_NAME)
 
-/*
- * DECLARE_AUTOMATA_HELPERS - define a set of helper functions for automata
- *
- * Define a set of helper functions for automata. The 'name' argument is used
- * as suffix for the functions and data. These functions will handle automaton
- * with data type 'type'.
- */
-#define DECLARE_AUTOMATA_HELPERS(name, type)
-
 /*
  * model_get_state_name - return the (string) name of the given state
  */
@@ -54,7 +44,7 @@ static char *model_get_event_name(enum events event)
 /*
  * model_get_initial_state - return the automaton's initial state
  */
-static inline type model_get_initial_state(void)
+static inline enum states model_get_initial_state(void)
 {
 	return RV_AUTOMATON_NAME.initial_state;
 }
@@ -65,8 +55,8 @@ static inline type model_get_initial_state(void)
  * Given the current state (curr_state) and the event (event), returns
  * the next state, or INVALID_STATE in case of error.
  */
-static inline type model_get_next_state(enum states curr_state,
-					       enum events event)
+static inline enum states model_get_next_state(enum states curr_state,
+					enum events event)
 {
 	if ((curr_state < 0) || (curr_state >= STATE_MAX))
 		return INVALID_STATE;
@@ -87,3 +77,5 @@ static inline bool model_is_final_state(enum states state)
 
 	return RV_AUTOMATON_NAME.final_states[state];
 }
+
+#endif
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index c2bc1350bb40..7c3540d0f64e 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -11,6 +11,9 @@
  *   Documentation/trace/rv/da_monitor_synthesis.rst
  */
 
+#ifndef _RV_DA_MONITOR_H
+#define _RV_DA_MONITOR_H
+
 #include <rv/automata.h>
 #include <linux/rv.h>
 #include <linux/stringify.h>
@@ -22,33 +25,20 @@ static struct rv_monitor rv_this;
 
 #ifdef CONFIG_RV_REACTORS
 
-#define DECLARE_RV_REACTING_HELPERS(name, type)
-static void cond_react(type curr_state, type event)
+static void cond_react(enum states curr_state, enum events event)
 {
 	if (!rv_reacting_on() || !rv_this.react)
 		return;
 	rv_this.react("rv: monitor %s does not allow event %s on state %s\n",
-			__stringify(MONITOR_NAME),
-			model_get_event_name(event),
-			model_get_state_name(curr_state));
+		      __stringify(MONITOR_NAME), model_get_event_name(event),
+		      model_get_state_name(curr_state));
 }
 
 #else /* CONFIG_RV_REACTOR */
 
-#define DECLARE_RV_REACTING_HELPERS(name, type)
-static void cond_react(type curr_state, type event)
-{
-	return;
-}
+static void cond_react(enum states curr_state, enum events event) { }
 #endif
 
-/*
- * Generic helpers for all types of deterministic automata monitors.
- */
-#define DECLARE_DA_MON_GENERIC_HELPERS(name, type)
-
-DECLARE_RV_REACTING_HELPERS(name, type)
-
 /*
  * da_monitor_reset - reset a monitor and setting it to init state
  */
@@ -99,7 +89,6 @@ static inline bool da_monitor_enabled(void)
  */
 static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
 {
-
 	if (!da_monitor_enabled())
 		return 0;
 
@@ -110,6 +99,7 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
 	return 1;
 }
 
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
 /*
  * Event handler for implicit monitors. Implicit monitor is the one which the
  * handler does not need to specify which da_monitor to manipulate. Examples
@@ -119,10 +109,8 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
  * warn and reset the monitor if it runs out of retries. The monitor should be
  * able to handle various orders.
  */
-#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
 
-static inline bool
-da_event(struct da_monitor *da_mon, enum events event)
+static inline bool da_event(struct da_monitor *da_mon, enum events event)
 {
 	enum states curr_state, next_state;
 
@@ -131,15 +119,17 @@ da_event(struct da_monitor *da_mon, enum events event)
 		next_state = model_get_next_state(curr_state, event);
 		if (next_state == INVALID_STATE) {
 			cond_react(curr_state, event);
-			CONCATENATE(trace_error_, MONITOR_NAME)(model_get_state_name(curr_state),
-					   model_get_event_name(event));
+			CONCATENATE(trace_error_, MONITOR_NAME)(
+				    model_get_state_name(curr_state),
+				    model_get_event_name(event));
 			return false;
 		}
 		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
-			CONCATENATE(trace_event_, MONITOR_NAME)(model_get_state_name(curr_state),
-					   model_get_event_name(event),
-					   model_get_state_name(next_state),
-					   model_is_final_state(next_state));
+			CONCATENATE(trace_event_, MONITOR_NAME)(
+				    model_get_state_name(curr_state),
+				    model_get_event_name(event),
+				    model_get_state_name(next_state),
+				    model_is_final_state(next_state));
 			return true;
 		}
 	}
@@ -151,6 +141,7 @@ da_event(struct da_monitor *da_mon, enum events event)
 	return false;
 }
 
+#elif RV_MON_TYPE == RV_MON_PER_TASK
 /*
  * Event handler for per_task monitors.
  *
@@ -158,10 +149,9 @@ da_event(struct da_monitor *da_mon, enum events event)
  * warn and reset the monitor if it runs out of retries. The monitor should be
  * able to handle various orders.
  */
-#elif RV_MON_TYPE == RV_MON_PER_TASK
 
 static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
-				   enum events event)
+			    enum events event)
 {
 	enum states curr_state, next_state;
 
@@ -171,16 +161,16 @@ static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
 		if (next_state == INVALID_STATE) {
 			cond_react(curr_state, event);
 			CONCATENATE(trace_error_, MONITOR_NAME)(tsk->pid,
-					   model_get_state_name(curr_state),
-					   model_get_event_name(event));
+				    model_get_state_name(curr_state),
+				    model_get_event_name(event));
 			return false;
 		}
 		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
 			CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
-					   model_get_state_name(curr_state),
-					   model_get_event_name(event),
-					   model_get_state_name(next_state),
-					   model_is_final_state(next_state));
+				    model_get_state_name(curr_state),
+				    model_get_event_name(event),
+				    model_get_state_name(next_state),
+				    model_is_final_state(next_state));
 			return true;
 		}
 	}
@@ -191,12 +181,12 @@ static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
 		model_get_event_name(event), __stringify(MONITOR_NAME));
 	return false;
 }
-#endif
+#endif /* RV_MON_TYPE */
 
+#if RV_MON_TYPE == RV_MON_GLOBAL
 /*
  * Functions to define, init and get a global monitor.
  */
-#if RV_MON_TYPE == RV_MON_GLOBAL
 
 /*
  * global monitor (a single variable)
@@ -231,15 +221,12 @@ static inline int da_monitor_init(void)
 /*
  * da_monitor_destroy - destroy the monitor
  */
-static inline void da_monitor_destroy(void)
-{
-	return;
-}
+static inline void da_monitor_destroy(void) { }
 
+#elif RV_MON_TYPE == RV_MON_PER_CPU
 /*
  * Functions to define, init and get a per-cpu monitor.
  */
-#elif RV_MON_TYPE == RV_MON_PER_CPU
 
 /*
  * per-cpu monitor variables
@@ -261,6 +248,7 @@ static void da_monitor_reset_all(void)
 {
 	struct da_monitor *da_mon;
 	int cpu;
+
 	for_each_cpu(cpu, cpu_online_mask) {
 		da_mon = per_cpu_ptr(&RV_DA_MON_NAME, cpu);
 		da_monitor_reset(da_mon);
@@ -279,15 +267,12 @@ static inline int da_monitor_init(void)
 /*
  * da_monitor_destroy - destroy the monitor
  */
-static inline void da_monitor_destroy(void)
-{
-	return;
-}
+static inline void da_monitor_destroy(void) { }
 
+#elif RV_MON_TYPE == RV_MON_PER_TASK
 /*
  * Functions to define, init and get a per-task monitor.
  */
-#elif RV_MON_TYPE == RV_MON_PER_TASK
 
 /*
  * The per-task monitor is stored a vector in the task struct. This variable
@@ -347,18 +332,17 @@ static inline void da_monitor_destroy(void)
 	}
 	rv_put_task_monitor_slot(task_mon_slot);
 	task_mon_slot = RV_PER_TASK_MONITOR_INIT;
-	return;
 }
-#endif
+#endif /* RV_MON_TYPE */
 
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
 /*
  * Handle event for implicit monitor: da_get_monitor() will figure out
  * the monitor.
  */
-#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
 
 static inline void __da_handle_event(struct da_monitor *da_mon,
-					    enum events event)
+				     enum events event)
 {
 	bool retval;
 
@@ -434,14 +418,13 @@ static inline bool da_handle_start_run_event(enum events event)
 	return 1;
 }
 
+#elif RV_MON_TYPE == RV_MON_PER_TASK
 /*
  * Handle event for per task.
  */
-#elif RV_MON_TYPE == RV_MON_PER_TASK
 
-static inline void
-__da_handle_event(struct da_monitor *da_mon, struct task_struct *tsk,
-			 enum events event)
+static inline void __da_handle_event(struct da_monitor *da_mon,
+				     struct task_struct *tsk, enum events event)
 {
 	bool retval;
 
@@ -453,8 +436,7 @@ __da_handle_event(struct da_monitor *da_mon, struct task_struct *tsk,
 /*
  * da_handle_event - handle an event
  */
-static inline void
-da_handle_event(struct task_struct *tsk, enum events event)
+static inline void da_handle_event(struct task_struct *tsk, enum events event)
 {
 	struct da_monitor *da_mon = da_get_monitor(tsk);
 	bool retval;
@@ -476,8 +458,8 @@ da_handle_event(struct task_struct *tsk, enum events event)
  * If the monitor already started, handle the event.
  * If the monitor did not start yet, start the monitor but skip the event.
  */
-static inline bool
-da_handle_start_event(struct task_struct *tsk, enum events event)
+static inline bool da_handle_start_event(struct task_struct *tsk,
+					 enum events event)
 {
 	struct da_monitor *da_mon;
 
@@ -502,8 +484,8 @@ da_handle_start_event(struct task_struct *tsk, enum events event)
  * This function is used to notify the monitor that the system is in the
  * initial state, so the monitor can start monitoring and handling event.
  */
-static inline bool
-da_handle_start_run_event(struct task_struct *tsk, enum events event)
+static inline bool da_handle_start_run_event(struct task_struct *tsk,
+					     enum events event)
 {
 	struct da_monitor *da_mon;
 
@@ -519,19 +501,6 @@ da_handle_start_run_event(struct task_struct *tsk, enum events event)
 
 	return 1;
 }
-#endif
-
-/*
- * Entry point for the global monitor.
- */
-#define DECLARE_DA_MON_GLOBAL(name, type)
-
-/*
- * Entry point for the per-cpu monitor.
- */
-#define DECLARE_DA_MON_PER_CPU(name, type)
+#endif /* RV_MON_TYPE */
 
-/*
- * Entry point for the per-task monitor.
- */
-#define DECLARE_DA_MON_PER_TASK(name, type)
+#endif
-- 
2.51.0


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

* [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 02/20] rv: Cleanup da_monitor after refactor Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  9:14   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
                   ` (17 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The DA event handling functions are mostly duplicated because the
per-task monitors need to propagate the task to use the pid in the trace
events. This is a maintenance burden for a little advantage.
The task can be obtained with some pointer arithmetic from the da_mon,
hence only the function tracing events really need to differ.

Unify all code handling the events, create da_trace_event() and
da_trace_error() that only call the tracepoint function.
Propagate the monitor id through the calls, the do_trace_ functions use
the id (pid) in case of per-task monitors but ignore it for implicit
monitors.

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

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 7c3540d0f64e..05b216f90f93 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -23,6 +23,13 @@
 #define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
 static struct rv_monitor rv_this;
 
+/*
+ * Type for the target id, default to int but can be overridden.
+ */
+#ifndef da_id_type
+#define da_id_type int
+#endif
+
 #ifdef CONFIG_RV_REACTORS
 
 static void cond_react(enum states curr_state, enum events event)
@@ -99,90 +106,6 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
 	return 1;
 }
 
-#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
-/*
- * Event handler for implicit monitors. Implicit monitor is the one which the
- * handler does not need to specify which da_monitor to manipulate. Examples
- * of implicit monitor are the per_cpu or the global ones.
- *
- * Retry in case there is a race between getting and setting the next state,
- * warn and reset the monitor if it runs out of retries. The monitor should be
- * able to handle various orders.
- */
-
-static inline bool da_event(struct da_monitor *da_mon, enum events event)
-{
-	enum states curr_state, next_state;
-
-	curr_state = READ_ONCE(da_mon->curr_state);
-	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
-		next_state = model_get_next_state(curr_state, event);
-		if (next_state == INVALID_STATE) {
-			cond_react(curr_state, event);
-			CONCATENATE(trace_error_, MONITOR_NAME)(
-				    model_get_state_name(curr_state),
-				    model_get_event_name(event));
-			return false;
-		}
-		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
-			CONCATENATE(trace_event_, MONITOR_NAME)(
-				    model_get_state_name(curr_state),
-				    model_get_event_name(event),
-				    model_get_state_name(next_state),
-				    model_is_final_state(next_state));
-			return true;
-		}
-	}
-
-	trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
-	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
-		" retries reached for event %s, resetting monitor %s",
-		model_get_event_name(event), __stringify(MONITOR_NAME));
-	return false;
-}
-
-#elif RV_MON_TYPE == RV_MON_PER_TASK
-/*
- * Event handler for per_task monitors.
- *
- * Retry in case there is a race between getting and setting the next state,
- * warn and reset the monitor if it runs out of retries. The monitor should be
- * able to handle various orders.
- */
-
-static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
-			    enum events event)
-{
-	enum states curr_state, next_state;
-
-	curr_state = READ_ONCE(da_mon->curr_state);
-	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
-		next_state = model_get_next_state(curr_state, event);
-		if (next_state == INVALID_STATE) {
-			cond_react(curr_state, event);
-			CONCATENATE(trace_error_, MONITOR_NAME)(tsk->pid,
-				    model_get_state_name(curr_state),
-				    model_get_event_name(event));
-			return false;
-		}
-		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
-			CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
-				    model_get_state_name(curr_state),
-				    model_get_event_name(event),
-				    model_get_state_name(next_state),
-				    model_is_final_state(next_state));
-			return true;
-		}
-	}
-
-	trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
-	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
-		" retries reached for event %s, resetting monitor %s",
-		model_get_event_name(event), __stringify(MONITOR_NAME));
-	return false;
-}
-#endif /* RV_MON_TYPE */
-
 #if RV_MON_TYPE == RV_MON_GLOBAL
 /*
  * Functions to define, init and get a global monitor.
@@ -288,6 +211,14 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
 	return &tsk->rv[task_mon_slot].da_mon;
 }
 
+/*
+ * da_get_task - return the task associated to the monitor
+ */
+static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
+{
+	return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon);
+}
+
 static void da_monitor_reset_all(void)
 {
 	struct task_struct *g, *p;
@@ -337,33 +268,144 @@ static inline void da_monitor_destroy(void)
 
 #if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
 /*
- * Handle event for implicit monitor: da_get_monitor() will figure out
- * the monitor.
+ * Trace events for implicit monitors. Implicit monitor is the one which the
+ * handler does not need to specify which da_monitor to manipulate. Examples
+ * of implicit monitor are the per_cpu or the global ones.
  */
 
-static inline void __da_handle_event(struct da_monitor *da_mon,
-				     enum events event)
+static inline void da_trace_event(struct da_monitor *da_mon,
+				  char *curr_state, char *event,
+				  char *next_state, bool is_final,
+				  da_id_type id)
+{
+	CONCATENATE(trace_event_, MONITOR_NAME)(curr_state, event, next_state,
+						is_final);
+}
+
+static inline void da_trace_error(struct da_monitor *da_mon,
+				  char *curr_state, char *event,
+				  da_id_type id)
+{
+	CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * Trace events for per_task monitors, report the PID of the task.
+ */
+
+static inline void da_trace_event(struct da_monitor *da_mon,
+				  char *curr_state, char *event,
+				  char *next_state, bool is_final,
+				  da_id_type id)
+{
+	CONCATENATE(trace_event_, MONITOR_NAME)(id, curr_state, event,
+						next_state, is_final);
+}
+
+static inline void da_trace_error(struct da_monitor *da_mon,
+				  char *curr_state, char *event,
+				  da_id_type id)
+{
+	CONCATENATE(trace_error_, MONITOR_NAME)(id, curr_state, event);
+}
+#endif /* RV_MON_TYPE */
+
+/*
+ * da_event - handle an event for the da_mon
+ *
+ * This function is valid for both implicit and id monitors.
+ * Retry in case there is a race between getting and setting the next state,
+ * warn and reset the monitor if it runs out of retries. The monitor should be
+ * able to handle various orders.
+ */
+static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_type id)
+{
+	enum states curr_state, next_state;
+
+	curr_state = READ_ONCE(da_mon->curr_state);
+	for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
+		next_state = model_get_next_state(curr_state, event);
+		if (next_state == INVALID_STATE) {
+			cond_react(curr_state, event);
+			da_trace_error(da_mon, model_get_state_name(curr_state),
+				       model_get_event_name(event), id);
+			return false;
+		}
+		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+			da_trace_event(da_mon, model_get_state_name(curr_state),
+				       model_get_event_name(event),
+				       model_get_state_name(next_state),
+				       model_is_final_state(next_state), id);
+			return true;
+		}
+	}
+
+	trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
+	pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
+		" retries reached for event %s, resetting monitor %s",
+		model_get_event_name(event), __stringify(MONITOR_NAME));
+	return false;
+}
+
+static inline void __da_handle_event_common(struct da_monitor *da_mon,
+					    enum events event, da_id_type id)
 {
 	bool retval;
 
-	retval = da_event(da_mon, event);
+	retval = da_event(da_mon, event, id);
 	if (!retval)
 		da_monitor_reset(da_mon);
 }
 
-/*
- * da_handle_event - handle an event
- */
-static inline void da_handle_event(enum events event)
+static inline void __da_handle_event(struct da_monitor *da_mon,
+				     enum events event, da_id_type id)
 {
-	struct da_monitor *da_mon = da_get_monitor();
 	bool retval;
 
 	retval = da_monitor_handling_event(da_mon);
 	if (!retval)
 		return;
 
-	__da_handle_event(da_mon, event);
+	__da_handle_event_common(da_mon, event, id);
+}
+
+static inline bool __da_handle_start_event(struct da_monitor *da_mon,
+					   enum events event, da_id_type id)
+{
+	if (unlikely(!da_monitoring(da_mon))) {
+		da_monitor_start(da_mon);
+		return 0;
+	}
+
+	__da_handle_event_common(da_mon, event, id);
+
+	return 1;
+}
+
+static inline bool __da_handle_start_run_event(struct da_monitor *da_mon,
+					       enum events event, da_id_type id)
+{
+	if (unlikely(!da_monitoring(da_mon)))
+		da_monitor_start(da_mon);
+
+	__da_handle_event_common(da_mon, event, id);
+
+	return 1;
+}
+
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+/*
+ * Handle event for implicit monitor: da_get_monitor() will figure out
+ * the monitor.
+ */
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event(enum events event)
+{
+	__da_handle_event(da_get_monitor(), event, 0);
 }
 
 /*
@@ -378,21 +420,9 @@ static inline void da_handle_event(enum events event)
  */
 static inline bool da_handle_start_event(enum events event)
 {
-	struct da_monitor *da_mon;
-
 	if (!da_monitor_enabled())
 		return 0;
-
-	da_mon = da_get_monitor();
-
-	if (unlikely(!da_monitoring(da_mon))) {
-		da_monitor_start(da_mon);
-		return 0;
-	}
-
-	__da_handle_event(da_mon, event);
-
-	return 1;
+	return __da_handle_start_event(da_get_monitor(), event, 0);
 }
 
 /*
@@ -403,19 +433,9 @@ static inline bool da_handle_start_event(enum events event)
  */
 static inline bool da_handle_start_run_event(enum events event)
 {
-	struct da_monitor *da_mon;
-
 	if (!da_monitor_enabled())
 		return 0;
-
-	da_mon = da_get_monitor();
-
-	if (unlikely(!da_monitoring(da_mon)))
-		da_monitor_start(da_mon);
-
-	__da_handle_event(da_mon, event);
-
-	return 1;
+	return __da_handle_start_run_event(da_get_monitor(), event, 0);
 }
 
 #elif RV_MON_TYPE == RV_MON_PER_TASK
@@ -423,29 +443,12 @@ static inline bool da_handle_start_run_event(enum events event)
  * Handle event for per task.
  */
 
-static inline void __da_handle_event(struct da_monitor *da_mon,
-				     struct task_struct *tsk, enum events event)
-{
-	bool retval;
-
-	retval = da_event(da_mon, tsk, event);
-	if (!retval)
-		da_monitor_reset(da_mon);
-}
-
 /*
  * da_handle_event - handle an event
  */
 static inline void da_handle_event(struct task_struct *tsk, enum events event)
 {
-	struct da_monitor *da_mon = da_get_monitor(tsk);
-	bool retval;
-
-	retval = da_monitor_handling_event(da_mon);
-	if (!retval)
-		return;
-
-	__da_handle_event(da_mon, tsk, event);
+	__da_handle_event(da_get_monitor(tsk), event, tsk->pid);
 }
 
 /*
@@ -461,21 +464,9 @@ static inline void da_handle_event(struct task_struct *tsk, enum events event)
 static inline bool da_handle_start_event(struct task_struct *tsk,
 					 enum events event)
 {
-	struct da_monitor *da_mon;
-
 	if (!da_monitor_enabled())
 		return 0;
-
-	da_mon = da_get_monitor(tsk);
-
-	if (unlikely(!da_monitoring(da_mon))) {
-		da_monitor_start(da_mon);
-		return 0;
-	}
-
-	__da_handle_event(da_mon, tsk, event);
-
-	return 1;
+	return __da_handle_start_event(da_get_monitor(tsk), event, tsk->pid);
 }
 
 /*
@@ -487,19 +478,9 @@ static inline bool da_handle_start_event(struct task_struct *tsk,
 static inline bool da_handle_start_run_event(struct task_struct *tsk,
 					     enum events event)
 {
-	struct da_monitor *da_mon;
-
 	if (!da_monitor_enabled())
 		return 0;
-
-	da_mon = da_get_monitor(tsk);
-
-	if (unlikely(!da_monitoring(da_mon)))
-		da_monitor_start(da_mon);
-
-	__da_handle_event(da_mon, tsk, event);
-
-	return 1;
+	return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid);
 }
 #endif /* RV_MON_TYPE */
 
-- 
2.51.0


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

* [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (2 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  9:26   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
                   ` (16 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
	linux-doc
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Previous changes refactored the da_monitor header file to avoid using
macros. This implies a few changes in how to import and use da_monitor
helpers:

 DECLARE_DA_MON_<TYPE>(name, type) is substituted by
 #define RV_MON_TYPE RV_MON_<TYPE>

 da_handle_event_<name>() is substituted by
 da_handle_event()

Update the documentation to reflect the changes.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 Documentation/trace/rv/monitor_synthesis.rst | 44 ++++++++++----------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/Documentation/trace/rv/monitor_synthesis.rst b/Documentation/trace/rv/monitor_synthesis.rst
index ac808a7554f5..ce0c1a5104d4 100644
--- a/Documentation/trace/rv/monitor_synthesis.rst
+++ b/Documentation/trace/rv/monitor_synthesis.rst
@@ -100,54 +100,52 @@ rv/da_monitor.h
 
 This initial implementation presents three different types of monitor instances:
 
-- ``#define DECLARE_DA_MON_GLOBAL(name, type)``
-- ``#define DECLARE_DA_MON_PER_CPU(name, type)``
-- ``#define DECLARE_DA_MON_PER_TASK(name, type)``
+- ``#define RV_MON_TYPE RV_MON_GLOBAL``
+- ``#define RV_MON_TYPE RV_MON_PER_CPU``
+- ``#define RV_MON_TYPE RV_MON_PER_TASK``
 
-The first declares the functions for a global deterministic automata monitor,
-the second for monitors with per-cpu instances, and the third with per-task
-instances.
+The first sets up functions declaration for a global deterministic automata
+monitor, the second for monitors with per-cpu instances, and the third with
+per-task instances.
 
-In all cases, the 'name' argument is a string that identifies the monitor, and
-the 'type' argument is the data type used by rvgen on the representation of
-the model in C.
+In all cases, the C file must include the $(MODEL_NAME).h file (generated by
+`rvgen`), for example, to define the per-cpu 'wip' monitor, the `wip.c` source
+file must include::
 
-For example, the wip model with two states and three events can be
-stored in an 'unsigned char' type. Considering that the preemption control
-is a per-cpu behavior, the monitor declaration in the 'wip.c' file is::
-
-  DECLARE_DA_MON_PER_CPU(wip, unsigned char);
+  #define RV_MON_TYPE RV_MON_PER_CPU
+  #include "wip.h"
+  #include <rv/da_monitor.h>
 
 The monitor is executed by sending events to be processed via the functions
 presented below::
 
-  da_handle_event_$(MONITOR_NAME)($(event from event enum));
-  da_handle_start_event_$(MONITOR_NAME)($(event from event enum));
-  da_handle_start_run_event_$(MONITOR_NAME)($(event from event enum));
+  da_handle_event($(event from event enum));
+  da_handle_start_event($(event from event enum));
+  da_handle_start_run_event($(event from event enum));
 
-The function ``da_handle_event_$(MONITOR_NAME)()`` is the regular case where
+The function ``da_handle_event()`` is the regular case where
 the event will be processed if the monitor is processing events.
 
 When a monitor is enabled, it is placed in the initial state of the automata.
 However, the monitor does not know if the system is in the *initial state*.
 
-The ``da_handle_start_event_$(MONITOR_NAME)()`` function is used to notify the
+The ``da_handle_start_event()`` function is used to notify the
 monitor that the system is returning to the initial state, so the monitor can
 start monitoring the next event.
 
-The ``da_handle_start_run_event_$(MONITOR_NAME)()`` function is used to notify
+The ``da_handle_start_run_event()`` function is used to notify
 the monitor that the system is known to be in the initial state, so the
 monitor can start monitoring and monitor the current event.
 
 Using the wip model as example, the events "preempt_disable" and
 "sched_waking" should be sent to monitor, respectively, via [2]::
 
-  da_handle_event_wip(preempt_disable_wip);
-  da_handle_event_wip(sched_waking_wip);
+  da_handle_event(preempt_disable_wip);
+  da_handle_event(sched_waking_wip);
 
 While the event "preempt_enabled" will use::
 
-  da_handle_start_event_wip(preempt_enable_wip);
+  da_handle_start_event(preempt_enable_wip);
 
 To notify the monitor that the system will be returning to the initial state,
 so the system and the monitor should be in sync.
-- 
2.51.0


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

* [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (3 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  9:34   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types Gabriele Monaco
                   ` (15 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Previous changes refactored the da_monitor header file to avoid using
macros. This implies a few changes in how to import and use da_monitor
helpers:

 DECLARE_DA_MON_<TYPE>(name, type) is substituted by
 #define RV_MON_TYPE RV_MON_<TYPE>

Update the rvgen templates to reflect the changes.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/rvgen/dot2k.py       |  6 +++--
 .../rvgen/rvgen/templates/dot2k/main.c        | 25 +++++++------------
 2 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index ed0a3c901106..d618a842fc52 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -38,9 +38,9 @@ class dot2k(Monitor, Dot2c):
                 handle = "handle_start_run_event"
             if self.monitor_type == "per_task":
                 buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;");
-                buff.append("\tda_%s_%s(p, %s%s);" % (handle, self.name, event, self.enum_suffix));
+                buff.append("\tda_%s(p, %s%s);" % (handle, event, self.enum_suffix));
             else:
-                buff.append("\tda_%s_%s(%s%s);" % (handle, self.name, event, self.enum_suffix));
+                buff.append("\tda_%s(%s%s);" % (handle, event, self.enum_suffix));
             buff.append("}")
             buff.append("")
         return '\n'.join(buff)
@@ -66,6 +66,8 @@ class dot2k(Monitor, Dot2c):
         buff.append(" *   Documentation/trace/rv/deterministic_automata.rst")
         buff.append(" */")
         buff.append("")
+        buff.append("#define MONITOR_NAME %s" % (self.name))
+        buff.append("")
 
         return buff
 
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index e0fd1134bd85..a14e4f0883db 100644
--- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -6,7 +6,6 @@
 #include <linux/init.h>
 #include <linux/rv.h>
 #include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
 
 #define MODULE_NAME "%%MODEL_NAME%%"
 
@@ -20,15 +19,9 @@
  * This is the self-generated part of the monitor. Generally, there is no need
  * to touch this section.
  */
+#define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
 #include "%%MODEL_NAME%%.h"
-
-/*
- * Declare the deterministic automata monitor.
- *
- * The rv monitor reference is needed for the monitor declaration.
- */
-static struct rv_monitor rv_%%MODEL_NAME%%;
-DECLARE_DA_MON_%%MONITOR_TYPE%%(%%MODEL_NAME%%, %%MIN_TYPE%%);
+#include <rv/da_monitor.h>
 
 /*
  * This is the instrumentation part of the monitor.
@@ -42,7 +35,7 @@ static int enable_%%MODEL_NAME%%(void)
 {
 	int retval;
 
-	retval = da_monitor_init_%%MODEL_NAME%%();
+	retval = da_monitor_init();
 	if (retval)
 		return retval;
 
@@ -53,33 +46,33 @@ static int enable_%%MODEL_NAME%%(void)
 
 static void disable_%%MODEL_NAME%%(void)
 {
-	rv_%%MODEL_NAME%%.enabled = 0;
+	rv_this.enabled = 0;
 
 %%TRACEPOINT_DETACH%%
 
-	da_monitor_destroy_%%MODEL_NAME%%();
+	da_monitor_destroy();
 }
 
 /*
  * 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%%,
 	.disable = disable_%%MODEL_NAME%%,
-	.reset = da_monitor_reset_all_%%MODEL_NAME%%,
+	.reset = da_monitor_reset_all,
 	.enabled = 0,
 };
 
 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%%);
-- 
2.51.0


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

* [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (4 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  9:39   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 07/20] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
                   ` (14 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Functions in automata.py, dot2c.py and dot2k.py don't have type
annotations and it can get complicated to remember how to use them.

Add minimal type annotations.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/rvgen/automata.py | 20 +++++------
 tools/verification/rvgen/rvgen/dot2c.py    | 42 +++++++++++-----------
 tools/verification/rvgen/rvgen/dot2k.py    | 20 +++++------
 3 files changed, 41 insertions(+), 41 deletions(-)

diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index d9a3fe2b74bf..3f06aef8d4fd 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -28,7 +28,7 @@ class Automata:
         self.function = self.__create_matrix()
         self.events_start, self.events_start_run = self.__store_init_events()
 
-    def __get_model_name(self):
+    def __get_model_name(self) -> str:
         basename = ntpath.basename(self.__dot_path)
         if not basename.endswith(".dot") and not basename.endswith(".gv"):
             print("not a dot file")
@@ -40,7 +40,7 @@ class Automata:
 
         return model_name
 
-    def __open_dot(self):
+    def __open_dot(self) -> list[str]:
         cursor = 0
         dot_lines = []
         try:
@@ -60,13 +60,13 @@ class Automata:
             cursor += 1
         return dot_lines
 
-    def __get_cursor_begin_states(self):
+    def __get_cursor_begin_states(self) -> int:
         cursor = 0
         while self.__dot_lines[cursor].split()[0] != "{node":
             cursor += 1
         return cursor
 
-    def __get_cursor_begin_events(self):
+    def __get_cursor_begin_events(self) -> int:
         cursor = 0
         while self.__dot_lines[cursor].split()[0] != "{node":
             cursor += 1
@@ -76,7 +76,7 @@ class Automata:
         cursor += 1
         return cursor
 
-    def __get_state_variables(self):
+    def __get_state_variables(self) -> tuple[list[str], str, list[str]]:
         # wait for node declaration
         states = []
         final_states = []
@@ -116,7 +116,7 @@ class Automata:
 
         return states, initial_state, final_states
 
-    def __get_event_variables(self):
+    def __get_event_variables(self) -> list[str]:
         # here we are at the begin of transitions, take a note, we will return later.
         cursor = self.__get_cursor_begin_events()
 
@@ -140,7 +140,7 @@ class Automata:
 
         return sorted(set(events))
 
-    def __create_matrix(self):
+    def __create_matrix(self) -> list[list[str]]:
         # transform the array into a dictionary
         events = self.events
         states = self.states
@@ -174,7 +174,7 @@ class Automata:
 
         return matrix
 
-    def __store_init_events(self):
+    def __store_init_events(self) -> tuple[list[bool], list[bool]]:
         events_start = [False] * len(self.events)
         events_start_run = [False] * len(self.events)
         for i, _ in enumerate(self.events):
@@ -196,10 +196,10 @@ class Automata:
                 events_start_run[i] = True
         return events_start, events_start_run
 
-    def is_start_event(self, event):
+    def is_start_event(self, event: str) -> bool:
         return self.events_start[self.events.index(event)]
 
-    def is_start_run_event(self, event):
+    def is_start_run_event(self, event: str) -> bool:
         # prefer handle_start_event if there
         if any(self.events_start):
             return False
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index b9b6f14cc536..fd64174fcfad 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -35,7 +35,7 @@ class Dot2c(Automata):
         # cut off the last \n
         return string[:-1]
 
-    def __get_enum_states_content(self):
+    def __get_enum_states_content(self) -> list[str]:
         buff = []
         buff.append("\t%s%s = 0," % (self.initial_state, self.enum_suffix))
         for state in self.states:
@@ -49,7 +49,7 @@ class Dot2c(Automata):
         buff = self.__get_enum_states_content()
         return self.__buff_to_string(buff)
 
-    def format_states_enum(self):
+    def format_states_enum(self) -> list[str]:
         buff = []
         buff.append("enum %s {" % self.enum_states_def)
         buff.append(self.get_enum_states_string())
@@ -57,7 +57,7 @@ class Dot2c(Automata):
 
         return buff
 
-    def __get_enum_events_content(self):
+    def __get_enum_events_content(self) -> list[str]:
         buff = []
         first = True
         for event in self.events:
@@ -75,7 +75,7 @@ class Dot2c(Automata):
         buff = self.__get_enum_events_content()
         return self.__buff_to_string(buff)
 
-    def format_events_enum(self):
+    def format_events_enum(self) -> list[str]:
         buff = []
         buff.append("enum %s {" % self.enum_events_def)
         buff.append(self.get_enum_events_string())
@@ -83,7 +83,7 @@ class Dot2c(Automata):
 
         return buff
 
-    def get_minimun_type(self):
+    def get_minimun_type(self) -> str:
         min_type = "unsigned char"
 
         if self.states.__len__() > 255:
@@ -97,7 +97,7 @@ class Dot2c(Automata):
 
         return min_type
 
-    def format_automaton_definition(self):
+    def format_automaton_definition(self) -> list[str]:
         min_type = self.get_minimun_type()
         buff = []
         buff.append("struct %s {" % self.struct_automaton_def)
@@ -109,12 +109,12 @@ class Dot2c(Automata):
         buff.append("};\n")
         return buff
 
-    def format_aut_init_header(self):
+    def format_aut_init_header(self) -> list[str]:
         buff = []
         buff.append("static const struct %s %s = {" % (self.struct_automaton_def, self.var_automaton_def))
         return buff
 
-    def __get_string_vector_per_line_content(self, buff):
+    def __get_string_vector_per_line_content(self, buff: list[str]) -> str:
         first = True
         string = ""
         for entry in buff:
@@ -133,14 +133,14 @@ class Dot2c(Automata):
     def get_aut_init_states_string(self):
         return self.__get_string_vector_per_line_content(self.states)
 
-    def format_aut_init_events_string(self):
+    def format_aut_init_events_string(self) -> list[str]:
         buff = []
         buff.append("\t.event_names = {")
         buff.append(self.get_aut_init_events_string())
         buff.append("\t},")
         return buff
 
-    def format_aut_init_states_string(self):
+    def format_aut_init_states_string(self) -> list[str]:
         buff = []
         buff.append("\t.state_names = {")
         buff.append(self.get_aut_init_states_string())
@@ -148,11 +148,11 @@ class Dot2c(Automata):
 
         return buff
 
-    def __get_max_strlen_of_states(self):
+    def __get_max_strlen_of_states(self) -> int:
         max_state_name = max(self.states, key = len).__len__()
         return max(max_state_name, self.invalid_state_str.__len__())
 
-    def get_aut_init_function(self):
+    def get_aut_init_function(self) -> str:
         nr_states = self.states.__len__()
         nr_events = self.events.__len__()
         buff = []
@@ -180,7 +180,7 @@ class Dot2c(Automata):
 
         return self.__buff_to_string(buff)
 
-    def format_aut_init_function(self):
+    def format_aut_init_function(self) -> list[str]:
         buff = []
         buff.append("\t.function = {")
         buff.append(self.get_aut_init_function())
@@ -188,17 +188,17 @@ class Dot2c(Automata):
 
         return buff
 
-    def get_aut_init_initial_state(self):
+    def get_aut_init_initial_state(self) -> str:
         return self.initial_state
 
-    def format_aut_init_initial_state(self):
+    def format_aut_init_initial_state(self) -> list[str]:
         buff = []
         initial_state = self.get_aut_init_initial_state()
         buff.append("\t.initial_state = " + initial_state + self.enum_suffix + ",")
 
         return buff
 
-    def get_aut_init_final_states(self):
+    def get_aut_init_final_states(self) -> str:
         line = ""
         first = True
         for state in self.states:
@@ -213,29 +213,29 @@ class Dot2c(Automata):
                 line = line + '0'
         return line
 
-    def format_aut_init_final_states(self):
+    def format_aut_init_final_states(self) -> list[str]:
        buff = []
        buff.append("\t.final_states = { %s }," % self.get_aut_init_final_states())
 
        return buff
 
-    def __get_automaton_initialization_footer_string(self):
+    def __get_automaton_initialization_footer_string(self) -> str:
         footer = "};\n"
         return footer
 
-    def format_aut_init_footer(self):
+    def format_aut_init_footer(self) -> list[str]:
         buff = []
         buff.append(self.__get_automaton_initialization_footer_string())
 
         return buff
 
-    def format_invalid_state(self):
+    def format_invalid_state(self) -> list[str]:
         buff = []
         buff.append("#define %s state_max%s\n" % (self.invalid_state_str, self.enum_suffix))
 
         return buff
 
-    def format_model(self):
+    def format_model(self) -> list[str]:
         buff = []
         buff += self.format_states_enum()
         buff += self.format_invalid_state()
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index d618a842fc52..6128fe238430 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -21,10 +21,10 @@ class dot2k(Monitor, Dot2c):
         Dot2c.__init__(self, file_path, extra_params.get("model_name"))
         self.enum_suffix = "_%s" % self.name
 
-    def fill_monitor_type(self):
+    def fill_monitor_type(self) -> str:
         return self.monitor_type.upper()
 
-    def fill_tracepoint_handlers_skel(self):
+    def fill_tracepoint_handlers_skel(self) -> str:
         buff = []
         for event in self.events:
             buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
@@ -45,19 +45,19 @@ class dot2k(Monitor, Dot2c):
             buff.append("")
         return '\n'.join(buff)
 
-    def fill_tracepoint_attach_probe(self):
+    def fill_tracepoint_attach_probe(self) -> str:
         buff = []
         for event in self.events:
             buff.append("\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
         return '\n'.join(buff)
 
-    def fill_tracepoint_detach_helper(self):
+    def fill_tracepoint_detach_helper(self) -> str:
         buff = []
         for event in self.events:
             buff.append("\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
         return '\n'.join(buff)
 
-    def fill_model_h_header(self):
+    def fill_model_h_header(self) -> list[str]:
         buff = []
         buff.append("/* SPDX-License-Identifier: GPL-2.0 */")
         buff.append("/*")
@@ -71,7 +71,7 @@ class dot2k(Monitor, Dot2c):
 
         return buff
 
-    def fill_model_h(self):
+    def fill_model_h(self) -> str:
         #
         # Adjust the definition names
         #
@@ -85,17 +85,17 @@ class dot2k(Monitor, Dot2c):
 
         return '\n'.join(buff)
 
-    def fill_monitor_class_type(self):
+    def fill_monitor_class_type(self) -> str:
         if self.monitor_type == "per_task":
             return "DA_MON_EVENTS_ID"
         return "DA_MON_EVENTS_IMPLICIT"
 
-    def fill_monitor_class(self):
+    def fill_monitor_class(self) -> str:
         if self.monitor_type == "per_task":
             return "da_monitor_id"
         return "da_monitor"
 
-    def fill_tracepoint_args_skel(self, tp_type):
+    def fill_tracepoint_args_skel(self, tp_type: str) -> str:
         buff = []
         tp_args_event = [
                 ("char *", "state"),
@@ -117,7 +117,7 @@ class dot2k(Monitor, Dot2c):
         buff.append("	     TP_ARGS(%s)" % tp_args_c)
         return '\n'.join(buff)
 
-    def fill_main_c(self):
+    def fill_main_c(self) -> str:
         main_c = super().fill_main_c()
 
         min_type = self.get_minimun_type()
-- 
2.51.0


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

* [PATCH v2 07/20] verification/dot2c: Remove __buff_to_string() and cleanup
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (5 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma Gabriele Monaco
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

str.join() can do what __buff_to_string() does. Therefore replace
__buff_to_string() to make the scripts more pythonic.

Also clean and remove some intermediate functions.

Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/rvgen/dot2c.py | 35 +++++--------------------
 1 file changed, 6 insertions(+), 29 deletions(-)

diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index fd64174fcfad..24894411c3cd 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -26,15 +26,6 @@ class Dot2c(Automata):
         super().__init__(file_path, model_name)
         self.line_length = 100
 
-    def __buff_to_string(self, buff):
-        string = ""
-
-        for line in buff:
-            string = string + line + "\n"
-
-        # cut off the last \n
-        return string[:-1]
-
     def __get_enum_states_content(self) -> list[str]:
         buff = []
         buff.append("\t%s%s = 0," % (self.initial_state, self.enum_suffix))
@@ -45,14 +36,10 @@ class Dot2c(Automata):
 
         return buff
 
-    def get_enum_states_string(self):
-        buff = self.__get_enum_states_content()
-        return self.__buff_to_string(buff)
-
     def format_states_enum(self) -> list[str]:
         buff = []
         buff.append("enum %s {" % self.enum_states_def)
-        buff.append(self.get_enum_states_string())
+        buff += self.__get_enum_states_content()
         buff.append("};\n")
 
         return buff
@@ -71,14 +58,10 @@ class Dot2c(Automata):
 
         return buff
 
-    def get_enum_events_string(self):
-        buff = self.__get_enum_events_content()
-        return self.__buff_to_string(buff)
-
     def format_events_enum(self) -> list[str]:
         buff = []
         buff.append("enum %s {" % self.enum_events_def)
-        buff.append(self.get_enum_events_string())
+        buff += self.__get_enum_events_content()
         buff.append("};\n")
 
         return buff
@@ -127,23 +110,17 @@ class Dot2c(Automata):
 
         return string
 
-    def get_aut_init_events_string(self):
-        return self.__get_string_vector_per_line_content(self.events)
-
-    def get_aut_init_states_string(self):
-        return self.__get_string_vector_per_line_content(self.states)
-
     def format_aut_init_events_string(self) -> list[str]:
         buff = []
         buff.append("\t.event_names = {")
-        buff.append(self.get_aut_init_events_string())
+        buff.append(self.__get_string_vector_per_line_content(self.events))
         buff.append("\t},")
         return buff
 
     def format_aut_init_states_string(self) -> list[str]:
         buff = []
         buff.append("\t.state_names = {")
-        buff.append(self.get_aut_init_states_string())
+        buff.append(self.__get_string_vector_per_line_content(self.states))
         buff.append("\t},")
 
         return buff
@@ -178,7 +155,7 @@ class Dot2c(Automata):
                     line += "\n\t\t}," if linetoolong else " },"
             buff.append(line)
 
-        return self.__buff_to_string(buff)
+        return '\n'.join(buff)
 
     def format_aut_init_function(self) -> list[str]:
         buff = []
@@ -253,4 +230,4 @@ class Dot2c(Automata):
 
     def print_model_classic(self):
         buff = self.format_model()
-        print(self.__buff_to_string(buff))
+        print('\n'.join(buff))
-- 
2.51.0


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

* [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (6 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 07/20] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02  9:40   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 09/20] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
                   ` (12 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The header files generated by dot2c currently create enums for states
and events assigning the first element to 0. This is superfluous as it
happens automatically if no value is specified.
Also it doesn't add a comma to the last enum elements, which slightly
complicates the diff if states or events are added.

Remove the assignment to 0 and add a comma to last elements, this
simplifies the logic for the code generator.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/monitors/nrp/nrp.h      | 20 +++++++-------
 kernel/trace/rv/monitors/opid/opid.h    | 22 +++++++--------
 kernel/trace/rv/monitors/sco/sco.h      | 12 ++++-----
 kernel/trace/rv/monitors/scpd/scpd.h    | 12 ++++-----
 kernel/trace/rv/monitors/snep/snep.h    | 16 +++++------
 kernel/trace/rv/monitors/snroc/snroc.h  | 12 ++++-----
 kernel/trace/rv/monitors/sssw/sssw.h    | 20 +++++++-------
 kernel/trace/rv/monitors/sts/sts.h      | 26 +++++++++---------
 kernel/trace/rv/monitors/wip/wip.h      | 12 ++++-----
 kernel/trace/rv/monitors/wwnr/wwnr.h    | 12 ++++-----
 tools/verification/rvgen/rvgen/dot2c.py | 36 +++++++++----------------
 11 files changed, 94 insertions(+), 106 deletions(-)

diff --git a/kernel/trace/rv/monitors/nrp/nrp.h b/kernel/trace/rv/monitors/nrp/nrp.h
index c2ec83da2124..3270d4c0139f 100644
--- a/kernel/trace/rv/monitors/nrp/nrp.h
+++ b/kernel/trace/rv/monitors/nrp/nrp.h
@@ -8,21 +8,21 @@
 #define MONITOR_NAME nrp
 
 enum states_nrp {
-	preempt_irq_nrp = 0,
+	preempt_irq_nrp,
 	any_thread_running_nrp,
 	nested_preempt_nrp,
 	rescheduling_nrp,
-	state_max_nrp
+	state_max_nrp,
 };
 
 #define INVALID_STATE state_max_nrp
 
 enum events_nrp {
-	irq_entry_nrp = 0,
+	irq_entry_nrp,
 	sched_need_resched_nrp,
 	schedule_entry_nrp,
 	schedule_entry_preempt_nrp,
-	event_max_nrp
+	event_max_nrp,
 };
 
 struct automaton_nrp {
@@ -38,38 +38,38 @@ static const struct automaton_nrp automaton_nrp = {
 		"preempt_irq",
 		"any_thread_running",
 		"nested_preempt",
-		"rescheduling"
+		"rescheduling",
 	},
 	.event_names = {
 		"irq_entry",
 		"sched_need_resched",
 		"schedule_entry",
-		"schedule_entry_preempt"
+		"schedule_entry_preempt",
 	},
 	.function = {
 		{
 			preempt_irq_nrp,
 			preempt_irq_nrp,
 			nested_preempt_nrp,
-			nested_preempt_nrp
+			nested_preempt_nrp,
 		},
 		{
 			any_thread_running_nrp,
 			rescheduling_nrp,
 			any_thread_running_nrp,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			nested_preempt_nrp,
 			preempt_irq_nrp,
 			any_thread_running_nrp,
-			any_thread_running_nrp
+			any_thread_running_nrp,
 		},
 		{
 			preempt_irq_nrp,
 			rescheduling_nrp,
 			any_thread_running_nrp,
-			any_thread_running_nrp
+			any_thread_running_nrp,
 		},
 	},
 	.initial_state = preempt_irq_nrp,
diff --git a/kernel/trace/rv/monitors/opid/opid.h b/kernel/trace/rv/monitors/opid/opid.h
index 5014f1b85ecf..092992514970 100644
--- a/kernel/trace/rv/monitors/opid/opid.h
+++ b/kernel/trace/rv/monitors/opid/opid.h
@@ -8,25 +8,25 @@
 #define MONITOR_NAME opid
 
 enum states_opid {
-	disabled_opid = 0,
+	disabled_opid,
 	enabled_opid,
 	in_irq_opid,
 	irq_disabled_opid,
 	preempt_disabled_opid,
-	state_max_opid
+	state_max_opid,
 };
 
 #define INVALID_STATE state_max_opid
 
 enum events_opid {
-	irq_disable_opid = 0,
+	irq_disable_opid,
 	irq_enable_opid,
 	irq_entry_opid,
 	preempt_disable_opid,
 	preempt_enable_opid,
 	sched_need_resched_opid,
 	sched_waking_opid,
-	event_max_opid
+	event_max_opid,
 };
 
 struct automaton_opid {
@@ -43,7 +43,7 @@ static const struct automaton_opid automaton_opid = {
 		"enabled",
 		"in_irq",
 		"irq_disabled",
-		"preempt_disabled"
+		"preempt_disabled",
 	},
 	.event_names = {
 		"irq_disable",
@@ -52,7 +52,7 @@ static const struct automaton_opid automaton_opid = {
 		"preempt_disable",
 		"preempt_enable",
 		"sched_need_resched",
-		"sched_waking"
+		"sched_waking",
 	},
 	.function = {
 		{
@@ -62,7 +62,7 @@ static const struct automaton_opid automaton_opid = {
 			INVALID_STATE,
 			irq_disabled_opid,
 			disabled_opid,
-			disabled_opid
+			disabled_opid,
 		},
 		{
 			irq_disabled_opid,
@@ -71,7 +71,7 @@ static const struct automaton_opid automaton_opid = {
 			preempt_disabled_opid,
 			enabled_opid,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			INVALID_STATE,
@@ -80,7 +80,7 @@ static const struct automaton_opid automaton_opid = {
 			INVALID_STATE,
 			INVALID_STATE,
 			in_irq_opid,
-			in_irq_opid
+			in_irq_opid,
 		},
 		{
 			INVALID_STATE,
@@ -89,7 +89,7 @@ static const struct automaton_opid automaton_opid = {
 			disabled_opid,
 			INVALID_STATE,
 			irq_disabled_opid,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			disabled_opid,
@@ -98,7 +98,7 @@ static const struct automaton_opid automaton_opid = {
 			INVALID_STATE,
 			enabled_opid,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 	},
 	.initial_state = disabled_opid,
diff --git a/kernel/trace/rv/monitors/sco/sco.h b/kernel/trace/rv/monitors/sco/sco.h
index 06b1c420ce54..bac3beb51e72 100644
--- a/kernel/trace/rv/monitors/sco/sco.h
+++ b/kernel/trace/rv/monitors/sco/sco.h
@@ -8,18 +8,18 @@
 #define MONITOR_NAME sco
 
 enum states_sco {
-	thread_context_sco = 0,
+	thread_context_sco,
 	scheduling_context_sco,
-	state_max_sco
+	state_max_sco,
 };
 
 #define INVALID_STATE state_max_sco
 
 enum events_sco {
-	sched_set_state_sco = 0,
+	sched_set_state_sco,
 	schedule_entry_sco,
 	schedule_exit_sco,
-	event_max_sco
+	event_max_sco,
 };
 
 struct automaton_sco {
@@ -33,12 +33,12 @@ struct automaton_sco {
 static const struct automaton_sco automaton_sco = {
 	.state_names = {
 		"thread_context",
-		"scheduling_context"
+		"scheduling_context",
 	},
 	.event_names = {
 		"sched_set_state",
 		"schedule_entry",
-		"schedule_exit"
+		"schedule_exit",
 	},
 	.function = {
 		{     thread_context_sco, scheduling_context_sco,          INVALID_STATE },
diff --git a/kernel/trace/rv/monitors/scpd/scpd.h b/kernel/trace/rv/monitors/scpd/scpd.h
index 4a725a68085a..d6329da2671b 100644
--- a/kernel/trace/rv/monitors/scpd/scpd.h
+++ b/kernel/trace/rv/monitors/scpd/scpd.h
@@ -8,19 +8,19 @@
 #define MONITOR_NAME scpd
 
 enum states_scpd {
-	cant_sched_scpd = 0,
+	cant_sched_scpd,
 	can_sched_scpd,
-	state_max_scpd
+	state_max_scpd,
 };
 
 #define INVALID_STATE state_max_scpd
 
 enum events_scpd {
-	preempt_disable_scpd = 0,
+	preempt_disable_scpd,
 	preempt_enable_scpd,
 	schedule_entry_scpd,
 	schedule_exit_scpd,
-	event_max_scpd
+	event_max_scpd,
 };
 
 struct automaton_scpd {
@@ -34,13 +34,13 @@ struct automaton_scpd {
 static const struct automaton_scpd automaton_scpd = {
 	.state_names = {
 		"cant_sched",
-		"can_sched"
+		"can_sched",
 	},
 	.event_names = {
 		"preempt_disable",
 		"preempt_enable",
 		"schedule_entry",
-		"schedule_exit"
+		"schedule_exit",
 	},
 	.function = {
 		{     can_sched_scpd,     INVALID_STATE,     INVALID_STATE,     INVALID_STATE },
diff --git a/kernel/trace/rv/monitors/snep/snep.h b/kernel/trace/rv/monitors/snep/snep.h
index 753080dc5fa1..357520a5b3d1 100644
--- a/kernel/trace/rv/monitors/snep/snep.h
+++ b/kernel/trace/rv/monitors/snep/snep.h
@@ -8,19 +8,19 @@
 #define MONITOR_NAME snep
 
 enum states_snep {
-	non_scheduling_context_snep = 0,
+	non_scheduling_context_snep,
 	scheduling_contex_snep,
-	state_max_snep
+	state_max_snep,
 };
 
 #define INVALID_STATE state_max_snep
 
 enum events_snep {
-	preempt_disable_snep = 0,
+	preempt_disable_snep,
 	preempt_enable_snep,
 	schedule_entry_snep,
 	schedule_exit_snep,
-	event_max_snep
+	event_max_snep,
 };
 
 struct automaton_snep {
@@ -34,26 +34,26 @@ struct automaton_snep {
 static const struct automaton_snep automaton_snep = {
 	.state_names = {
 		"non_scheduling_context",
-		"scheduling_contex"
+		"scheduling_contex",
 	},
 	.event_names = {
 		"preempt_disable",
 		"preempt_enable",
 		"schedule_entry",
-		"schedule_exit"
+		"schedule_exit",
 	},
 	.function = {
 		{
 			non_scheduling_context_snep,
 			non_scheduling_context_snep,
 			scheduling_contex_snep,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			INVALID_STATE,
 			INVALID_STATE,
 			INVALID_STATE,
-			non_scheduling_context_snep
+			non_scheduling_context_snep,
 		},
 	},
 	.initial_state = non_scheduling_context_snep,
diff --git a/kernel/trace/rv/monitors/snroc/snroc.h b/kernel/trace/rv/monitors/snroc/snroc.h
index ada5ee08bdab..88b7328ad31a 100644
--- a/kernel/trace/rv/monitors/snroc/snroc.h
+++ b/kernel/trace/rv/monitors/snroc/snroc.h
@@ -8,18 +8,18 @@
 #define MONITOR_NAME snroc
 
 enum states_snroc {
-	other_context_snroc = 0,
+	other_context_snroc,
 	own_context_snroc,
-	state_max_snroc
+	state_max_snroc,
 };
 
 #define INVALID_STATE state_max_snroc
 
 enum events_snroc {
-	sched_set_state_snroc = 0,
+	sched_set_state_snroc,
 	sched_switch_in_snroc,
 	sched_switch_out_snroc,
-	event_max_snroc
+	event_max_snroc,
 };
 
 struct automaton_snroc {
@@ -33,12 +33,12 @@ struct automaton_snroc {
 static const struct automaton_snroc automaton_snroc = {
 	.state_names = {
 		"other_context",
-		"own_context"
+		"own_context",
 	},
 	.event_names = {
 		"sched_set_state",
 		"sched_switch_in",
-		"sched_switch_out"
+		"sched_switch_out",
 	},
 	.function = {
 		{      INVALID_STATE,  own_context_snroc,       INVALID_STATE },
diff --git a/kernel/trace/rv/monitors/sssw/sssw.h b/kernel/trace/rv/monitors/sssw/sssw.h
index 8409eaadc7e0..1a4b806061c3 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.h
+++ b/kernel/trace/rv/monitors/sssw/sssw.h
@@ -8,17 +8,17 @@
 #define MONITOR_NAME sssw
 
 enum states_sssw {
-	runnable_sssw = 0,
+	runnable_sssw,
 	signal_wakeup_sssw,
 	sleepable_sssw,
 	sleeping_sssw,
-	state_max_sssw
+	state_max_sssw,
 };
 
 #define INVALID_STATE state_max_sssw
 
 enum events_sssw {
-	sched_set_state_runnable_sssw = 0,
+	sched_set_state_runnable_sssw,
 	sched_set_state_sleepable_sssw,
 	sched_switch_blocking_sssw,
 	sched_switch_in_sssw,
@@ -27,7 +27,7 @@ enum events_sssw {
 	sched_switch_yield_sssw,
 	sched_wakeup_sssw,
 	signal_deliver_sssw,
-	event_max_sssw
+	event_max_sssw,
 };
 
 struct automaton_sssw {
@@ -43,7 +43,7 @@ static const struct automaton_sssw automaton_sssw = {
 		"runnable",
 		"signal_wakeup",
 		"sleepable",
-		"sleeping"
+		"sleeping",
 	},
 	.event_names = {
 		"sched_set_state_runnable",
@@ -54,7 +54,7 @@ static const struct automaton_sssw automaton_sssw = {
 		"sched_switch_suspend",
 		"sched_switch_yield",
 		"sched_wakeup",
-		"signal_deliver"
+		"signal_deliver",
 	},
 	.function = {
 		{
@@ -66,7 +66,7 @@ static const struct automaton_sssw automaton_sssw = {
 			INVALID_STATE,
 			runnable_sssw,
 			runnable_sssw,
-			runnable_sssw
+			runnable_sssw,
 		},
 		{
 			INVALID_STATE,
@@ -77,7 +77,7 @@ static const struct automaton_sssw automaton_sssw = {
 			INVALID_STATE,
 			signal_wakeup_sssw,
 			signal_wakeup_sssw,
-			runnable_sssw
+			runnable_sssw,
 		},
 		{
 			runnable_sssw,
@@ -88,7 +88,7 @@ static const struct automaton_sssw automaton_sssw = {
 			sleeping_sssw,
 			signal_wakeup_sssw,
 			runnable_sssw,
-			sleepable_sssw
+			sleepable_sssw,
 		},
 		{
 			INVALID_STATE,
@@ -99,7 +99,7 @@ static const struct automaton_sssw automaton_sssw = {
 			INVALID_STATE,
 			INVALID_STATE,
 			runnable_sssw,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 	},
 	.initial_state = runnable_sssw,
diff --git a/kernel/trace/rv/monitors/sts/sts.h b/kernel/trace/rv/monitors/sts/sts.h
index 3779d7f99404..6f7b2d9d72e6 100644
--- a/kernel/trace/rv/monitors/sts/sts.h
+++ b/kernel/trace/rv/monitors/sts/sts.h
@@ -8,26 +8,26 @@
 #define MONITOR_NAME sts
 
 enum states_sts {
-	can_sched_sts = 0,
+	can_sched_sts,
 	cant_sched_sts,
 	disable_to_switch_sts,
 	enable_to_exit_sts,
 	in_irq_sts,
 	scheduling_sts,
 	switching_sts,
-	state_max_sts
+	state_max_sts,
 };
 
 #define INVALID_STATE state_max_sts
 
 enum events_sts {
-	irq_disable_sts = 0,
+	irq_disable_sts,
 	irq_enable_sts,
 	irq_entry_sts,
 	sched_switch_sts,
 	schedule_entry_sts,
 	schedule_exit_sts,
-	event_max_sts
+	event_max_sts,
 };
 
 struct automaton_sts {
@@ -46,7 +46,7 @@ static const struct automaton_sts automaton_sts = {
 		"enable_to_exit",
 		"in_irq",
 		"scheduling",
-		"switching"
+		"switching",
 	},
 	.event_names = {
 		"irq_disable",
@@ -54,7 +54,7 @@ static const struct automaton_sts automaton_sts = {
 		"irq_entry",
 		"sched_switch",
 		"schedule_entry",
-		"schedule_exit"
+		"schedule_exit",
 	},
 	.function = {
 		{
@@ -63,7 +63,7 @@ static const struct automaton_sts automaton_sts = {
 			INVALID_STATE,
 			INVALID_STATE,
 			scheduling_sts,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			INVALID_STATE,
@@ -71,7 +71,7 @@ static const struct automaton_sts automaton_sts = {
 			cant_sched_sts,
 			INVALID_STATE,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			INVALID_STATE,
@@ -79,7 +79,7 @@ static const struct automaton_sts automaton_sts = {
 			in_irq_sts,
 			switching_sts,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			enable_to_exit_sts,
@@ -87,7 +87,7 @@ static const struct automaton_sts automaton_sts = {
 			enable_to_exit_sts,
 			INVALID_STATE,
 			INVALID_STATE,
-			can_sched_sts
+			can_sched_sts,
 		},
 		{
 			INVALID_STATE,
@@ -95,7 +95,7 @@ static const struct automaton_sts automaton_sts = {
 			in_irq_sts,
 			INVALID_STATE,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			disable_to_switch_sts,
@@ -103,7 +103,7 @@ static const struct automaton_sts automaton_sts = {
 			INVALID_STATE,
 			INVALID_STATE,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 		{
 			INVALID_STATE,
@@ -111,7 +111,7 @@ static const struct automaton_sts automaton_sts = {
 			INVALID_STATE,
 			INVALID_STATE,
 			INVALID_STATE,
-			INVALID_STATE
+			INVALID_STATE,
 		},
 	},
 	.initial_state = can_sched_sts,
diff --git a/kernel/trace/rv/monitors/wip/wip.h b/kernel/trace/rv/monitors/wip/wip.h
index cfdc52975354..b4c3eea94c86 100644
--- a/kernel/trace/rv/monitors/wip/wip.h
+++ b/kernel/trace/rv/monitors/wip/wip.h
@@ -8,18 +8,18 @@
 #define MONITOR_NAME wip
 
 enum states_wip {
-	preemptive_wip = 0,
+	preemptive_wip,
 	non_preemptive_wip,
-	state_max_wip
+	state_max_wip,
 };
 
 #define INVALID_STATE state_max_wip
 
 enum events_wip {
-	preempt_disable_wip = 0,
+	preempt_disable_wip,
 	preempt_enable_wip,
 	sched_waking_wip,
-	event_max_wip
+	event_max_wip,
 };
 
 struct automaton_wip {
@@ -33,12 +33,12 @@ struct automaton_wip {
 static const struct automaton_wip automaton_wip = {
 	.state_names = {
 		"preemptive",
-		"non_preemptive"
+		"non_preemptive",
 	},
 	.event_names = {
 		"preempt_disable",
 		"preempt_enable",
-		"sched_waking"
+		"sched_waking",
 	},
 	.function = {
 		{ non_preemptive_wip,      INVALID_STATE,      INVALID_STATE },
diff --git a/kernel/trace/rv/monitors/wwnr/wwnr.h b/kernel/trace/rv/monitors/wwnr/wwnr.h
index 85d12e42a955..a28006512c9b 100644
--- a/kernel/trace/rv/monitors/wwnr/wwnr.h
+++ b/kernel/trace/rv/monitors/wwnr/wwnr.h
@@ -8,18 +8,18 @@
 #define MONITOR_NAME wwnr
 
 enum states_wwnr {
-	not_running_wwnr = 0,
+	not_running_wwnr,
 	running_wwnr,
-	state_max_wwnr
+	state_max_wwnr,
 };
 
 #define INVALID_STATE state_max_wwnr
 
 enum events_wwnr {
-	switch_in_wwnr = 0,
+	switch_in_wwnr,
 	switch_out_wwnr,
 	wakeup_wwnr,
-	event_max_wwnr
+	event_max_wwnr,
 };
 
 struct automaton_wwnr {
@@ -33,12 +33,12 @@ struct automaton_wwnr {
 static const struct automaton_wwnr automaton_wwnr = {
 	.state_names = {
 		"not_running",
-		"running"
+		"running",
 	},
 	.event_names = {
 		"switch_in",
 		"switch_out",
-		"wakeup"
+		"wakeup",
 	},
 	.function = {
 		{       running_wwnr,      INVALID_STATE,   not_running_wwnr },
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 24894411c3cd..06a26bf15a7e 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -28,11 +28,11 @@ class Dot2c(Automata):
 
     def __get_enum_states_content(self) -> list[str]:
         buff = []
-        buff.append("\t%s%s = 0," % (self.initial_state, self.enum_suffix))
+        buff.append("\t%s%s," % (self.initial_state, self.enum_suffix))
         for state in self.states:
             if state != self.initial_state:
                 buff.append("\t%s%s," % (state, self.enum_suffix))
-        buff.append("\tstate_max%s" % (self.enum_suffix))
+        buff.append("\tstate_max%s," % (self.enum_suffix))
 
         return buff
 
@@ -46,15 +46,10 @@ class Dot2c(Automata):
 
     def __get_enum_events_content(self) -> list[str]:
         buff = []
-        first = True
         for event in self.events:
-            if first:
-                buff.append("\t%s%s = 0," % (event, self.enum_suffix))
-                first = False
-            else:
-                buff.append("\t%s%s," % (event, self.enum_suffix))
+            buff.append("\t%s%s," % (event, self.enum_suffix))
 
-        buff.append("\tevent_max%s" % self.enum_suffix)
+        buff.append("\tevent_max%s," % self.enum_suffix)
 
         return buff
 
@@ -97,18 +92,11 @@ class Dot2c(Automata):
         buff.append("static const struct %s %s = {" % (self.struct_automaton_def, self.var_automaton_def))
         return buff
 
-    def __get_string_vector_per_line_content(self, buff: list[str]) -> str:
-        first = True
-        string = ""
-        for entry in buff:
-            if first:
-                string = string + "\t\t\"" + entry
-                first = False;
-            else:
-                string = string + "\",\n\t\t\"" + entry
-        string = string + "\""
-
-        return string
+    def __get_string_vector_per_line_content(self, entries: list[str]) -> str:
+        buff = []
+        for entry in entries:
+            buff.append(f"\t\t\"{entry}\",")
+        return "\n".join(buff)
 
     def format_aut_init_events_string(self) -> list[str]:
         buff = []
@@ -152,7 +140,7 @@ class Dot2c(Automata):
                 if y != nr_events-1:
                     line += ",\n" if linetoolong else ", "
                 else:
-                    line += "\n\t\t}," if linetoolong else " },"
+                    line += ",\n\t\t}," if linetoolong else " },"
             buff.append(line)
 
         return '\n'.join(buff)
@@ -179,12 +167,12 @@ class Dot2c(Automata):
         line = ""
         first = True
         for state in self.states:
-            if first == False:
+            if not first:
                 line = line + ', '
             else:
                 first = False
 
-            if self.final_states.__contains__(state):
+            if state in self.final_states:
                 line = line + '1'
             else:
                 line = line + '0'
-- 
2.51.0


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

* [PATCH v2 09/20] verification/rvgen: Remove unused variable declaration from containers
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (7 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 10/20] rv: Add Hybrid Automata monitor type Gabriele Monaco
                   ` (11 subsequent siblings)
  20 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The monitor container source files contained a declaration and a
definition for the rv_monitor variable. The former is superfluous and
can be removed.

Remove the variable declaration from the template as well as the
existing monitor containers.

Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/monitors/rtapp/rtapp.c                    | 2 --
 kernel/trace/rv/monitors/sched/sched.c                    | 2 --
 tools/verification/rvgen/rvgen/templates/container/main.c | 2 --
 3 files changed, 6 deletions(-)

diff --git a/kernel/trace/rv/monitors/rtapp/rtapp.c b/kernel/trace/rv/monitors/rtapp/rtapp.c
index fd75fc927d65..17f271231c99 100644
--- a/kernel/trace/rv/monitors/rtapp/rtapp.c
+++ b/kernel/trace/rv/monitors/rtapp/rtapp.c
@@ -8,8 +8,6 @@
 
 #include "rtapp.h"
 
-struct rv_monitor rv_rtapp;
-
 struct rv_monitor rv_rtapp = {
 	.name = "rtapp",
 	.description = "Collection of monitors for detecting problems with real-time applications",
diff --git a/kernel/trace/rv/monitors/sched/sched.c b/kernel/trace/rv/monitors/sched/sched.c
index d04db4b543f9..dd9d96fc6e21 100644
--- a/kernel/trace/rv/monitors/sched/sched.c
+++ b/kernel/trace/rv/monitors/sched/sched.c
@@ -8,8 +8,6 @@
 
 #include "sched.h"
 
-struct rv_monitor rv_sched;
-
 struct rv_monitor rv_sched = {
 	.name = "sched",
 	.description = "container for several scheduler monitor specifications.",
diff --git a/tools/verification/rvgen/rvgen/templates/container/main.c b/tools/verification/rvgen/rvgen/templates/container/main.c
index 7d9b2f95c7e9..5fc89b46f279 100644
--- a/tools/verification/rvgen/rvgen/templates/container/main.c
+++ b/tools/verification/rvgen/rvgen/templates/container/main.c
@@ -8,8 +8,6 @@
 
 #include "%%MODEL_NAME%%.h"
 
-struct rv_monitor rv_%%MODEL_NAME%%;
-
 struct rv_monitor rv_%%MODEL_NAME%% = {
 	.name = "%%MODEL_NAME%%",
 	.description = "%%DESCRIPTION%%",
-- 
2.51.0


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

* [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (8 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 09/20] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-17  8:44   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
                   ` (10 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Deterministic automata define which events are allowed in every state,
but cannot define more sophisticated constraint taking into account the
system's environment (e.g. time or other states not producing events).

Add the Hybrid Automata monitor type as an extension of Deterministic
automata where each state transition is validating a constraint on a
finite number of environment variables.
Hybrid automata can be used to implement timed automata, where the
environment variables are clocks.

Also implement the necessary functionality to handle clock constraints
(ns or jiffy granularity) on state and events.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/linux/rv.h         |  38 +++
 include/rv/da_monitor.h    |  59 ++++-
 include/rv/ha_monitor.h    | 469 +++++++++++++++++++++++++++++++++++++
 kernel/trace/rv/Kconfig    |  13 +
 kernel/trace/rv/rv_trace.h |  63 +++++
 5 files changed, 637 insertions(+), 5 deletions(-)
 create mode 100644 include/rv/ha_monitor.h

diff --git a/include/linux/rv.h b/include/linux/rv.h
index 3134681553b4..90d51e00e99c 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -83,11 +83,49 @@ struct ltl_monitor {};
 
 #endif /* CONFIG_RV_LTL_MONITOR */
 
+#ifdef CONFIG_RV_HA_MONITOR
+/*
+ * In the future, hybrid automata may rely on multiple
+ * environment variables, e.g. different clocks started at
+ * different times or running at different speed.
+ * For now we support only 1 variable.
+ */
+#define MAX_HA_ENV_LEN 1
+
+/*
+ * Monitors can pick the preferred timer implementation:
+ * No timer: if monitors don't have state invariants.
+ * Timer wheel: lightweight invariants check but far less precise.
+ * Hrtimer: accurate invariants check with higher overhead.
+ */
+#define HA_TIMER_NONE 0
+#define HA_TIMER_WHEEL 1
+#define HA_TIMER_HRTIMER 2
+
+/*
+ * Hybrid automaton per-object variables.
+ */
+struct ha_monitor {
+	struct da_monitor da_mon;
+	u64 env_store[MAX_HA_ENV_LEN];
+	union {
+		struct hrtimer hrtimer;
+		struct timer_list timer;
+	};
+};
+
+#else
+
+struct ha_monitor { };
+
+#endif /* CONFIG_RV_HA_MONITOR */
+
 #define RV_PER_TASK_MONITOR_INIT	(CONFIG_RV_PER_TASK_MONITORS)
 
 union rv_task_monitor {
 	struct da_monitor	da_mon;
 	struct ltl_monitor	ltl_mon;
+	struct ha_monitor	ha_mon;
 };
 
 #ifdef CONFIG_RV_REACTORS
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 05b216f90f93..afbe0807ed11 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -23,6 +23,33 @@
 #define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
 static struct rv_monitor rv_this;
 
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes curr_state, event and next_state and returns true if the
+ * environment constraints (e.g. timing) are satisfied, false otherwise.
+ */
+#ifndef da_monitor_event_hook
+#define da_monitor_event_hook(...) true
+#endif
+
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes the da_monitor and performs further initialisation
+ * (e.g. reset set up timers).
+ */
+#ifndef da_monitor_init_hook
+#define da_monitor_init_hook(da_mon)
+#endif
+
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes the da_monitor and performs further reset (e.g. reset
+ * all clocks).
+ */
+#ifndef da_monitor_reset_hook
+#define da_monitor_reset_hook(da_mon)
+#endif
+
 /*
  * Type for the target id, default to int but can be overridden.
  */
@@ -51,6 +78,7 @@ static void cond_react(enum states curr_state, enum events event) { }
  */
 static inline void da_monitor_reset(struct da_monitor *da_mon)
 {
+	da_monitor_reset_hook(da_mon);
 	da_mon->monitoring = 0;
 	da_mon->curr_state = model_get_initial_state();
 }
@@ -65,6 +93,7 @@ static inline void da_monitor_start(struct da_monitor *da_mon)
 {
 	da_mon->curr_state = model_get_initial_state();
 	da_mon->monitoring = 1;
+	da_monitor_init_hook(da_mon);
 }
 
 /*
@@ -114,14 +143,14 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
 /*
  * global monitor (a single variable)
  */
-static struct da_monitor RV_DA_MON_NAME;
+static union rv_task_monitor RV_DA_MON_NAME;
 
 /*
  * da_get_monitor - return the global monitor address
  */
 static struct da_monitor *da_get_monitor(void)
 {
-	return &RV_DA_MON_NAME;
+	return &(RV_DA_MON_NAME.da_mon);
 }
 
 /*
@@ -154,14 +183,14 @@ static inline void da_monitor_destroy(void) { }
 /*
  * per-cpu monitor variables
  */
-static DEFINE_PER_CPU(struct da_monitor, RV_DA_MON_NAME);
+static DEFINE_PER_CPU(union rv_task_monitor, RV_DA_MON_NAME);
 
 /*
  * da_get_monitor - return current CPU monitor address
  */
 static struct da_monitor *da_get_monitor(void)
 {
-	return this_cpu_ptr(&RV_DA_MON_NAME);
+	return &this_cpu_ptr(&RV_DA_MON_NAME)->da_mon;
 }
 
 /*
@@ -173,7 +202,7 @@ static void da_monitor_reset_all(void)
 	int cpu;
 
 	for_each_cpu(cpu, cpu_online_mask) {
-		da_mon = per_cpu_ptr(&RV_DA_MON_NAME, cpu);
+		da_mon = &per_cpu_ptr(&RV_DA_MON_NAME, cpu)->da_mon;
 		da_monitor_reset(da_mon);
 	}
 }
@@ -219,6 +248,16 @@ static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
 	return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon);
 }
 
+/*
+ * da_get_id - return the id associated to the monitor
+ *
+ * For per-task monitors, the id is the task's PID.
+ */
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+	return da_get_task(da_mon)->pid;
+}
+
 static void da_monitor_reset_all(void)
 {
 	struct task_struct *g, *p;
@@ -289,6 +328,14 @@ static inline void da_trace_error(struct da_monitor *da_mon,
 	CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
 }
 
+/*
+ * da_get_id - unused for implicit monitors
+ */
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+	return 0;
+}
+
 #elif RV_MON_TYPE == RV_MON_PER_TASK
 /*
  * Trace events for per_task monitors, report the PID of the task.
@@ -333,6 +380,8 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_
 			return false;
 		}
 		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+			if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
+				return false;
 			da_trace_event(da_mon, model_get_state_name(curr_state),
 				       model_get_event_name(event),
 				       model_get_state_name(next_state),
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
new file mode 100644
index 000000000000..fb885709b727
--- /dev/null
+++ b/include/rv/ha_monitor.h
@@ -0,0 +1,469 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025-2028 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * Hybrid automata (HA) monitor functions, to be used together
+ * with automata models in C generated by the dot2k tool.
+ *
+ * This type of monitors extends the Deterministic automata (DA) class by
+ * adding a set of environment variables (e.g. clocks) that can be used to
+ * constraint the valid transitions.
+ *
+ * The dot2k tool is available at tools/verification/dot2k/
+ *
+ * For further information, see:
+ *   Documentation/trace/rv/ha_monitor_synthesis.rst
+ */
+
+#ifndef _RV_HA_MONITOR_H
+#define _RV_HA_MONITOR_H
+
+#include <rv/automata.h>
+
+#ifndef da_id_type
+#define da_id_type int
+#endif
+
+static inline void ha_monitor_init_env(struct da_monitor *da_mon);
+static inline void ha_monitor_reset_env(struct da_monitor *da_mon);
+static inline void ha_setup_timer(struct ha_monitor *ha_mon);
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon);
+static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
+					 enum states curr_state,
+					 enum events event,
+					 enum states next_state,
+					 da_id_type id);
+#define da_monitor_event_hook ha_monitor_handle_constraint
+#define da_monitor_init_hook ha_monitor_init_env
+#define da_monitor_reset_hook ha_monitor_reset_env
+
+#include <rv/da_monitor.h>
+#include <linux/seq_buf.h>
+
+/* This simplifies things since da_mon and ha_mon coexist in the same union */
+_Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
+	       "da_mon must be the first element in an ha_mon!");
+#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
+
+#define ENV_MAX CONCATENATE(env_max_, MONITOR_NAME)
+#define ENV_MAX_STORED CONCATENATE(env_max_stored_, MONITOR_NAME)
+#define envs CONCATENATE(envs_, MONITOR_NAME)
+
+/* Environment storage before being reset */
+#define ENV_INVALID_VALUE U64_MAX
+/* Error with no event occurs only on timeouts */
+#define EVENT_NONE EVENT_MAX
+#define EVENT_NONE_LBL "none"
+#define ENV_BUFFER_SIZE 64
+
+#ifdef CONFIG_RV_REACTORS
+
+/*
+ * Trigger the reaction after a failed environment constraint (e.g. related to a
+ * clock).
+ */
+static void ha_cond_react(enum states curr_state, enum events event, char *env)
+{
+	if (!rv_reacting_on() || !rv_this.react)
+		return;
+	rv_this.react("rv: monitor %s does not allow event %s on state %s with env %s\n",
+			__stringify(MONITOR_NAME),
+			event == EVENT_NONE ? EVENT_NONE_LBL : model_get_event_name(event),
+			model_get_state_name(curr_state),
+			env);
+}
+
+#else /* CONFIG_RV_REACTOR */
+
+static void ha_cond_react(enum states curr_state, enum events event, char *env) { }
+#endif
+
+/*
+ * model_get_state_name - return the (string) name of the given state
+ */
+static char *model_get_env_name(enum envs env)
+{
+	if ((env < 0) || (env >= ENV_MAX))
+		return "INVALID";
+
+	return RV_AUTOMATON_NAME.env_names[env];
+}
+
+/*
+ * Monitors requiring a timer implementation need to request it explicitly.
+ */
+#ifndef HA_TIMER_TYPE
+#define HA_TIMER_TYPE HA_TIMER_NONE
+#endif
+
+#if HA_TIMER_TYPE == HA_TIMER_WHEEL
+static void ha_monitor_timer_callback(struct timer_list *timer);
+#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer);
+#endif
+
+/*
+ * ktime_get_ns is expensive, since we usually don't require precise accounting
+ * of changes within the same event, cache the current time at the beginning of
+ * the constraint handler and use the cache for subsequent calls.
+ * Monitors without ns clocks automatically skip this.
+ */
+#ifdef HA_CLK_NS
+#define ha_update_ns_cache() ktime_get_ns()
+#else
+#define ha_update_ns_cache() 0
+#endif /* HA_CLK_NS */
+
+/* Should be supplied by the monitor */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+				 enum states curr_state,
+				 enum events event,
+				 enum states next_state,
+				 u64 time_ns);
+
+/*
+ * ha_monitor_reset_all_stored - reset all environment variables in the monitor
+ */
+static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon)
+{
+	for (int i = 0; i < ENV_MAX_STORED; i++)
+		WRITE_ONCE(ha_mon->env_store[i], ENV_INVALID_VALUE);
+}
+
+/*
+ * ha_monitor_init_env - setup timer and reset all environment
+ *
+ * Called from a hook in the DA start functions, it supplies the da_mon
+ * corresponding to the current ha_mon.
+ * Not all hybrid automata require the timer, still set it for simplicity.
+ */
+static inline void ha_monitor_init_env(struct da_monitor *da_mon)
+{
+	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+
+	ha_monitor_reset_all_stored(ha_mon);
+	ha_setup_timer(ha_mon);
+}
+
+/*
+ * ha_monitor_reset_env - stop timer and reset all environment
+ *
+ * Called from a hook in the DA reset functions, it supplies the da_mon
+ * corresponding to the current ha_mon.
+ * Not all hybrid automata require the timer, still clear it for simplicity.
+ */
+static inline void ha_monitor_reset_env(struct da_monitor *da_mon)
+{
+	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+
+	ha_monitor_reset_all_stored(ha_mon);
+	/* Initialisation resets the monitor before initialising the timer */
+	if (likely(da_monitoring(da_mon)))
+		ha_cancel_timer(ha_mon);
+}
+
+/*
+ * ha_monitor_env_invalid - return true if env has not been initialised
+ */
+static inline bool ha_monitor_env_invalid(struct ha_monitor *ha_mon, enum envs env)
+{
+	return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
+}
+
+static inline void ha_get_env_string(struct seq_buf *s,
+				     struct ha_monitor *ha_mon, u64 time_ns)
+{
+	const char *format_str = "%s=%llu";
+
+	for (int i = 0; i < ENV_MAX; i++) {
+		seq_buf_printf(s, format_str, model_get_env_name(i),
+			       ha_get_env(ha_mon, i, time_ns));
+		format_str = ",%s=%llu";
+	}
+}
+
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
+				      char *curr_state, char *event, char *env,
+				      da_id_type id)
+{
+	CONCATENATE(trace_error_env_, MONITOR_NAME)(curr_state, event, env);
+}
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
+				      char *curr_state, char *event, char *env,
+				      da_id_type id)
+{
+	CONCATENATE(trace_error_env_, MONITOR_NAME)(id, curr_state, event, env);
+}
+#endif /* RV_MON_TYPE */
+
+/*
+ * ha_get_monitor - return the current monitor
+ */
+#define ha_get_monitor(...) to_ha_monitor(da_get_monitor(__VA_ARGS__))
+
+/*
+ * ha_monitor_handle_constraint - handle the constraint on the current transition
+ *
+ * If the monitor implementation defines a constraint in the transition from
+ * curr_state to event, react and trace appropriately as well as return false.
+ * This function is called from the hook in the DA event handle function and
+ * triggers a failure in the monitor.
+ */
+static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
+					 enum states curr_state,
+					 enum events event,
+					 enum states next_state,
+					 da_id_type id)
+{
+	struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+	u64 time_ns = ha_update_ns_cache();
+	DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE);
+
+	if (ha_verify_constraint(ha_mon, curr_state, event, next_state, time_ns))
+		return true;
+
+	ha_get_env_string(&env_string, ha_mon, time_ns);
+	ha_cond_react(curr_state, event, env_string.buffer);
+	ha_trace_error_env(ha_mon,
+			   model_get_state_name(curr_state),
+			   model_get_event_name(event),
+			   env_string.buffer, id);
+	return false;
+}
+
+static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon)
+{
+	enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state);
+	DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE);
+	u64 time_ns = ha_update_ns_cache();
+
+	ha_get_env_string(&env_string, ha_mon, time_ns);
+	ha_cond_react(curr_state, EVENT_NONE, env_string.buffer);
+	ha_trace_error_env(ha_mon, model_get_state_name(curr_state),
+			   EVENT_NONE_LBL, env_string.buffer,
+			   da_get_id(&ha_mon->da_mon));
+
+	da_monitor_reset(&ha_mon->da_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.
+ */
+
+/*
+ * Helper functions for env variables describing clocks with ns granularity
+ */
+static inline u64 ha_get_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
+{
+	return time_ns - READ_ONCE(ha_mon->env_store[env]);
+}
+static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
+{
+	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)
+{
+	return READ_ONCE(ha_mon->env_store[env]) >= time_ns;
+}
+static inline u64 ha_get_passed_ns(struct ha_monitor *ha_mon, enum envs env,
+				   u64 expire, 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;
+}
+
+/*
+ * Helper functions for env variables describing clocks with jiffy granularity
+ */
+static inline u64 ha_get_clk_jiffy(struct ha_monitor *ha_mon, enum envs env)
+{
+	return get_jiffies_64() - READ_ONCE(ha_mon->env_store[env]);
+}
+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)
+{
+	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());
+
+}
+static inline u64 ha_get_passed_jiffy(struct ha_monitor *ha_mon, enum envs env,
+				      u64 expire, 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;
+}
+
+/*
+ * Retrieve the last reset time (guard representation) from the invariant
+ * representation (expiration).
+ * It the caller's responsibility to make sure the storage was actually in the
+ * invariant representation (e.g. the current state has an invariant).
+ * The provided value must be the same used when starting the invariant.
+ *
+ * This function's access to the storage is NOT atomic, due to the rarity when
+ * this is used. If a monitor allows writes concurrent to this, likely
+ * other things are broken and need rethinking the model or additional locking.
+ */
+static inline void ha_inv_to_guard(struct ha_monitor *ha_mon, enum envs env,
+				   u64 value, u64 time_ns)
+{
+	WRITE_ONCE(ha_mon->env_store[env], READ_ONCE(ha_mon->env_store[env]) - value);
+}
+
+#if HA_TIMER_TYPE == HA_TIMER_WHEEL
+/*
+ * Helper functions to handle the monitor timer.
+ * Not all monitors require a timer, in such case the timer will be set up but
+ * never armed.
+ * Timers start since the last reset of the supplied env or from now if env is
+ * not an environment variable. If env was not initialised no timer starts.
+ * Timers can expire on any CPU unless the monitor is per-cpu,
+ * where we assume every event occurs on the local CPU.
+ */
+static void ha_monitor_timer_callback(struct timer_list *timer)
+{
+	struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer);
+
+	__ha_monitor_timer_callback(ha_mon);
+}
+static inline void ha_setup_timer(struct ha_monitor *ha_mon)
+{
+	int mode = 0;
+
+	if (RV_MON_TYPE == RV_MON_PER_CPU)
+		mode |= TIMER_PINNED;
+	timer_setup(&ha_mon->timer, ha_monitor_timer_callback, mode);
+}
+static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
+					u64 expire, u64 time_ns)
+{
+	u64 passed = ha_get_passed_jiffy(ha_mon, env, expire, 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_get_passed_ns(ha_mon, env, expire, time_ns);
+
+	ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
+			     nsecs_to_jiffies(expire - passed) + 1, time_ns);
+}
+/*
+ * ha_cancel_timer - Cancel the timer
+ *
+ * Returns:
+ *  *  1 when the timer was active
+ *  *  0 when the timer was not active or running a callback
+ */
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+	return timer_delete(&ha_mon->timer);
+}
+#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
+/*
+ * Helper functions to handle the monitor timer.
+ * Not all monitors require a timer, in such case the timer will be set up but
+ * never armed.
+ * Timers start since the last reset of the supplied env or from now if env is
+ * not an environment variable. If env was not initialised no timer starts.
+ * Timers can expire on any CPU unless the monitor is per-cpu,
+ * where we assume every event occurs on the local CPU.
+ */
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer)
+{
+	struct ha_monitor *ha_mon = container_of(hrtimer, struct ha_monitor, hrtimer);
+
+	__ha_monitor_timer_callback(ha_mon);
+	return HRTIMER_NORESTART;
+}
+static inline void ha_setup_timer(struct ha_monitor *ha_mon)
+{
+	hrtimer_setup(&ha_mon->hrtimer, ha_monitor_timer_callback,
+		      CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
+}
+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_get_passed_ns(ha_mon, env, expire, time_ns);
+
+	if (RV_MON_TYPE == RV_MON_PER_CPU)
+		mode |= HRTIMER_MODE_PINNED;
+	hrtimer_start(&ha_mon->hrtimer, ns_to_ktime(expire - passed), mode);
+}
+static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
+					u64 expire, u64 time_ns)
+{
+	u64 passed = ha_get_passed_jiffy(ha_mon, env, expire, time_ns);
+
+	ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
+			  jiffies_to_nsecs(expire - passed), time_ns);
+}
+/*
+ * ha_cancel_timer - Cancel the timer
+ *
+ * Returns:
+ *  *  1 when the timer was active
+ *  *  0 when the timer was not active or running a callback
+ */
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+	return hrtimer_try_to_cancel(&ha_mon->hrtimer) == 1;
+}
+#else //HA_TIMER_NONE
+/*
+ * Start function is intentionally not defined, monitors using timers must
+ * set HA_TIMER_TYPE to either HA_TIMER_WHEEL or HA_TIMER_HRTIMER.
+ */
+static inline void ha_setup_timer(struct ha_monitor *ha_mon) { }
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+	return false;
+}
+#endif
+
+#endif
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 5b4be87ba59d..4ad392dfc57f 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -23,6 +23,19 @@ config LTL_MON_EVENTS_ID
 config RV_LTL_MONITOR
 	bool
 
+config RV_HA_MONITOR
+	bool
+
+config HA_MON_EVENTS_IMPLICIT
+	select DA_MON_EVENTS_IMPLICIT
+	select RV_HA_MONITOR
+	bool
+
+config HA_MON_EVENTS_ID
+	select DA_MON_EVENTS_ID
+	select RV_HA_MONITOR
+	bool
+
 menuconfig RV
 	bool "Runtime Verification"
 	select TRACING
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 4a6faddac614..7c598967bc0e 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -65,6 +65,36 @@ DECLARE_EVENT_CLASS(error_da_monitor,
 #include <monitors/opid/opid_trace.h>
 // Add new monitors based on CONFIG_DA_MON_EVENTS_IMPLICIT here
 
+#ifdef CONFIG_HA_MON_EVENTS_IMPLICIT
+/* For simplicity this class is marked as DA although relevant only for HA */
+DECLARE_EVENT_CLASS(error_env_da_monitor,
+
+	TP_PROTO(char *state, char *event, char *env),
+
+	TP_ARGS(state, event, env),
+
+	TP_STRUCT__entry(
+		__string(	state,	state	)
+		__string(	event,	event	)
+		__string(	env,	env	)
+	),
+
+	TP_fast_assign(
+		__assign_str(state);
+		__assign_str(event);
+		__assign_str(env);
+	),
+
+	TP_printk("event %s not expected in the state %s with env %s",
+		__get_str(event),
+		__get_str(state),
+		__get_str(env))
+);
+
+// Add new monitors based on CONFIG_HA_MON_EVENTS_IMPLICIT here
+
+#endif
+
 #endif /* CONFIG_DA_MON_EVENTS_IMPLICIT */
 
 #ifdef CONFIG_DA_MON_EVENTS_ID
@@ -128,6 +158,39 @@ DECLARE_EVENT_CLASS(error_da_monitor_id,
 #include <monitors/sssw/sssw_trace.h>
 // Add new monitors based on CONFIG_DA_MON_EVENTS_ID here
 
+#ifdef CONFIG_HA_MON_EVENTS_ID
+/* For simplicity this class is marked as DA although relevant only for HA */
+DECLARE_EVENT_CLASS(error_env_da_monitor_id,
+
+	TP_PROTO(int id, char *state, char *event, char *env),
+
+	TP_ARGS(id, state, event, env),
+
+	TP_STRUCT__entry(
+		__field(	int,	id	)
+		__string(	state,	state	)
+		__string(	event,	event	)
+		__string(	env,	env	)
+	),
+
+	TP_fast_assign(
+		__assign_str(state);
+		__assign_str(event);
+		__assign_str(env);
+		__entry->id	= id;
+	),
+
+	TP_printk("%d: event %s not expected in the state %s with env %s",
+		__entry->id,
+		__get_str(event),
+		__get_str(state),
+		__get_str(env))
+);
+
+// Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
+
+#endif
+
 #endif /* CONFIG_DA_MON_EVENTS_ID */
 #ifdef CONFIG_LTL_MON_EVENTS_ID
 DECLARE_EVENT_CLASS(event_ltl_monitor_id,
-- 
2.51.0


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

* [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (9 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 10/20] rv: Add Hybrid Automata monitor type Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-02 11:03   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
                   ` (9 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Currently the automata parser assumes event strings don't have any
space, this stands true for event names, but can be a wrong assumption
if we want to store other information in the event strings (e.g.
constraints for hybrid automata).

Adapt the parser logic to allow spaces in the event strings.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/rvgen/automata.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index 3f06aef8d4fd..977ba859c34e 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -127,14 +127,13 @@ class Automata:
             #  ------------ event is here ------------^^^^^
             if self.__dot_lines[cursor].split()[1] == "->":
                 line = self.__dot_lines[cursor].split()
-                event = line[-2].replace('"','')
+                event = "".join(line[line.index("label")+2:-1]).replace('"','')
 
                 # when a transition has more than one lables, they are like this
                 # "local_irq_enable\nhw_local_irq_enable_n"
                 # so split them.
 
-                event = event.replace("\\n", " ")
-                for i in event.split():
+                for i in event.split("\\n"):
                     events.append(i)
             cursor += 1
 
@@ -167,8 +166,8 @@ class Automata:
                 line = self.__dot_lines[cursor].split()
                 origin_state = line[0].replace('"','').replace(',','_')
                 dest_state = line[2].replace('"','').replace(',','_')
-                possible_events = line[-2].replace('"','').replace("\\n", " ")
-                for event in possible_events.split():
+                possible_events = "".join(line[line.index("label")+2:-1]).replace('"','')
+                for event in possible_events.split("\\n"):
                     matrix[states_dict[origin_state]][events_dict[event]] = dest_state
             cursor += 1
 
-- 
2.51.0


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

* [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (10 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-17  9:37   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
                   ` (8 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Add the possibility to parse dot files as hybrid automata and generate
the necessary code from rvgen.

Hybrid automata are very similar to deterministic ones and most
functionality is shared, the dot files include also constraints together
with event names (separated by ;) and state names (separated by \n).

The tool can now generate the appropriate code to validate constraints
at runtime according to the dot specification.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/__main__.py          |   8 +-
 tools/verification/rvgen/rvgen/automata.py    | 141 +++++-
 tools/verification/rvgen/rvgen/dot2c.py       |  49 ++
 tools/verification/rvgen/rvgen/dot2k.py       | 474 +++++++++++++++++-
 tools/verification/rvgen/rvgen/generator.py   |   2 +
 .../rvgen/rvgen/templates/dot2k/main.c        |   2 +-
 .../rvgen/templates/dot2k/trace_hybrid.h      |  16 +
 7 files changed, 678 insertions(+), 14 deletions(-)
 create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h

diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index fa6fc1f4de2f..b8e07e463293 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -9,7 +9,7 @@
 #   Documentation/trace/rv/da_monitor_synthesis.rst
 
 if __name__ == '__main__':
-    from rvgen.dot2k import dot2k
+    from rvgen.dot2k import da2k, ha2k
     from rvgen.generator import Monitor
     from rvgen.container import Container
     from rvgen.ltl2k import ltl2k
@@ -29,7 +29,7 @@ if __name__ == '__main__':
     monitor_parser.add_argument("-p", "--parent", dest="parent",
                                 required=False, help="Create a monitor nested to parent")
     monitor_parser.add_argument('-c', "--class", dest="monitor_class",
-                                help="Monitor class, either \"da\" or \"ltl\"")
+                                help="Monitor class, either \"da\", \"ha\" or \"ltl\"")
     monitor_parser.add_argument('-s', "--spec", dest="spec", help="Monitor specification file")
     monitor_parser.add_argument('-t', "--monitor_type", dest="monitor_type",
                                 help=f"Available options: {', '.join(Monitor.monitor_types.keys())}")
@@ -43,7 +43,9 @@ if __name__ == '__main__':
         if params.subcmd == "monitor":
             print("Opening and parsing the specification file %s" % params.spec)
             if params.monitor_class == "da":
-                monitor = dot2k(params.spec, params.monitor_type, vars(params))
+                monitor = da2k(params.spec, params.monitor_type, vars(params))
+            elif params.monitor_class == "ha":
+                monitor = ha2k(params.spec, params.monitor_type, vars(params))
             elif params.monitor_class == "ltl":
                 monitor = ltl2k(params.spec, params.monitor_type, vars(params))
             else:
diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index 977ba859c34e..252c9960ee1c 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -9,24 +9,63 @@
 #   Documentation/trace/rv/deterministic_automata.rst
 
 import ntpath
+import re
+from typing import Iterator
+
+class _ConstraintKey:
+    """Base class for constraint keys."""
+
+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 _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))
 
 class Automata:
     """Automata class: Reads a dot file and part it as an automata.
 
+    It supports both deterministic and hybrid automata.
+
     Attributes:
         dot_file: A dot file with an state_automaton definition.
     """
 
     invalid_state_str = "INVALID_STATE"
+    # 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.events = self.__get_event_variables()
-        self.function = self.__create_matrix()
+        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.events_start, self.events_start_run = self.__store_init_events()
+        self.env_stored = sorted(self.env_stored)
+        self.constraint_vars = sorted(self.constraint_vars)
 
     def __get_model_name(self) -> str:
         basename = ntpath.basename(self.__dot_path)
@@ -116,11 +155,12 @@ class Automata:
 
         return states, initial_state, final_states
 
-    def __get_event_variables(self) -> list[str]:
+    def __get_event_variables(self) -> tuple[list[str], list[str]]:
         # here we are at the begin of transitions, take a note, we will return later.
         cursor = self.__get_cursor_begin_events()
 
         events = []
+        envs = []
         while self.__dot_lines[cursor].lstrip()[0] == '"':
             # transitions have the format:
             # "all_fired" -> "both_fired" [ label = "disable_irq" ];
@@ -134,12 +174,74 @@ class Automata:
                 # so split them.
 
                 for i in event.split("\\n"):
-                    events.append(i)
+                    # 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 ValueError("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 = self.__dot_lines[cursor].split("label")[1].split('"')[1]
+                _, *constr = state.split("\\n")
+                if constr:
+                    if len(constr) > 1:
+                        raise ValueError("Only 1 constraint is supported in the state")
+                    envs += self.__extract_env_var([constr[0].replace(" ", "")])
             cursor += 1
 
-        return sorted(set(events))
-
-    def __create_matrix(self) -> list[list[str]]:
+        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]]]:
         # transform the array into a dictionary
         events = self.events
         states = self.states
@@ -157,6 +259,7 @@ class Automata:
 
         # declare the matrix....
         matrix = [[ self.invalid_state_str for x in range(nr_event)] for y in range(nr_state)]
+        constraints: dict[_ConstraintKey, list[str]] = {}
 
         # and we are back! Let's fill the matrix
         cursor = self.__get_cursor_begin_events()
@@ -168,10 +271,22 @@ class Automata:
                 dest_state = line[2].replace('"','').replace(',','_')
                 possible_events = "".join(line[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 = self.__dot_lines[cursor].split("label")[1].split('"')[1]
+                state, *constr = state.replace(" ", "").split("\\n")
+                if constr:
+                    constraints[_StateConstraintKey(states_dict[state])] = constr
             cursor += 1
 
-        return matrix
+        return matrix, constraints
 
     def __store_init_events(self) -> tuple[list[bool], list[bool]]:
         events_start = [False] * len(self.events)
@@ -203,3 +318,13 @@ class Automata:
         if any(self.events_start):
             return False
         return self.events_start_run[self.events.index(event)]
+
+    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 06a26bf15a7e..be1f044f9f98 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -19,6 +19,7 @@ class Dot2c(Automata):
     enum_suffix = ""
     enum_states_def = "states"
     enum_events_def = "events"
+    enum_envs_def = "envs"
     struct_automaton_def = "automaton"
     var_automaton_def = "aut"
 
@@ -61,6 +62,39 @@ class Dot2c(Automata):
 
         return buff
 
+    def __get_non_stored_envs(self) -> list[str]:
+        return [ e for e in self.envs if e not in self.env_stored ]
+
+    def __get_enum_envs_content(self) -> list[str]:
+        buff = []
+        # We first place env variables that have a u64 storage.
+        # Those are limited by MAX_HA_ENV_LEN, other variables
+        # are read only and don't require a storage.
+        unstored = self.__get_non_stored_envs()
+        for env in list(self.env_stored) + unstored:
+            buff.append("\t%s%s," % (env, self.enum_suffix))
+
+        buff.append("\tenv_max%s," % self.enum_suffix)
+        buff.append("\tenv_max_stored%s = %s%s," %
+                    (self.enum_suffix,
+                     unstored[0] if len(unstored) else "env_max",
+                     self.enum_suffix))
+
+        return buff
+
+    def format_envs_enum(self) -> list[str]:
+        buff = []
+        if self.is_hybrid_automata():
+            buff.append("enum %s {" % self.enum_envs_def)
+            buff += self.__get_enum_envs_content()
+            buff.append("};\n")
+            buff.append('_Static_assert(env_max_stored%s <= MAX_HA_ENV_LEN, "Not enough slots");' %
+                        (self.enum_suffix))
+            if any(u in self.env_types.values() for u in ["ns", "us", "ms", "s"]):
+                buff.append("#define HA_CLK_NS")
+            buff.append("")
+        return buff
+
     def get_minimun_type(self) -> str:
         min_type = "unsigned char"
 
@@ -81,6 +115,8 @@ class Dot2c(Automata):
         buff.append("struct %s {" % self.struct_automaton_def)
         buff.append("\tchar *state_names[state_max%s];" % (self.enum_suffix))
         buff.append("\tchar *event_names[event_max%s];" % (self.enum_suffix))
+        if self.is_hybrid_automata():
+            buff.append("\tchar *env_names[env_max%s];" % (self.enum_suffix))
         buff.append("\t%s function[state_max%s][event_max%s];" % (min_type, self.enum_suffix, self.enum_suffix))
         buff.append("\t%s initial_state;" % min_type)
         buff.append("\tbool final_states[state_max%s];" % (self.enum_suffix))
@@ -113,6 +149,17 @@ class Dot2c(Automata):
 
         return buff
 
+    def format_aut_init_envs_string(self) -> list[str]:
+        buff = []
+        if self.is_hybrid_automata():
+            buff.append("\t.env_names = {")
+            # maintain consistent order with the enum
+            ordered_envs = list(self.env_stored) + self.__get_non_stored_envs()
+            buff.append(self.__get_string_vector_per_line_content(ordered_envs))
+            buff.append("\t},")
+
+        return buff
+
     def __get_max_strlen_of_states(self) -> int:
         max_state_name = max(self.states, key = len).__len__()
         return max(max_state_name, self.invalid_state_str.__len__())
@@ -205,10 +252,12 @@ class Dot2c(Automata):
         buff += self.format_states_enum()
         buff += self.format_invalid_state()
         buff += self.format_events_enum()
+        buff += self.format_envs_enum()
         buff += self.format_automaton_definition()
         buff += self.format_aut_init_header()
         buff += self.format_aut_init_states_string()
         buff += self.format_aut_init_events_string()
+        buff += self.format_aut_init_envs_string()
         buff += self.format_aut_init_function()
         buff += self.format_aut_init_initial_state()
         buff += self.format_aut_init_final_states()
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index 6128fe238430..627efaec3a59 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -8,8 +8,10 @@
 # For further information, see:
 #   Documentation/trace/rv/da_monitor_synthesis.rst
 
+from collections import deque
 from .dot2c import Dot2c
 from .generator import Monitor
+from .automata import _EventConstraintKey, _StateConstraintKey
 
 
 class dot2k(Monitor, Dot2c):
@@ -20,12 +22,16 @@ class dot2k(Monitor, Dot2c):
         Monitor.__init__(self, extra_params)
         Dot2c.__init__(self, file_path, extra_params.get("model_name"))
         self.enum_suffix = "_%s" % self.name
+        self.monitor_class = extra_params["monitor_class"]
 
     def fill_monitor_type(self) -> str:
-        return self.monitor_type.upper()
+        buff = [ self.monitor_type.upper() ]
+        buff += self._fill_timer_type()
+        return "\n".join(buff)
 
     def fill_tracepoint_handlers_skel(self) -> str:
         buff = []
+        buff += self._fill_hybrid_definitions()
         for event in self.events:
             buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
             buff.append("{")
@@ -77,6 +83,7 @@ class dot2k(Monitor, Dot2c):
         #
         self.enum_states_def = "states_%s" % self.name
         self.enum_events_def = "events_%s" % self.name
+        self.enum_envs_def = "envs_%s" % self.name
         self.struct_automaton_def = "automaton_%s" % self.name
         self.var_automaton_def = "automaton_%s" % self.name
 
@@ -107,8 +114,14 @@ class dot2k(Monitor, Dot2c):
                 ("char *", "state"),
                 ("char *", "event"),
                 ]
+        tp_args_error_env = tp_args_error + [("char *", "env")]
+        tp_args_dict = {
+                "event": tp_args_event,
+                "error": tp_args_error,
+                "error_env": tp_args_error_env
+                }
         tp_args_id = ("int ", "id")
-        tp_args = tp_args_event if tp_type == "event" else tp_args_error
+        tp_args = tp_args_dict[tp_type]
         if self.monitor_type == "per_task":
             tp_args.insert(0, tp_args_id)
         tp_proto_c = ", ".join([a+b for a,b in tp_args])
@@ -117,6 +130,14 @@ class dot2k(Monitor, Dot2c):
         buff.append("	     TP_ARGS(%s)" % tp_args_c)
         return '\n'.join(buff)
 
+    def _fill_hybrid_definitions(self) -> list:
+        """Stub, not valid for deterministic automata"""
+        return []
+
+    def _fill_timer_type(self) -> list:
+        """Stub, not valid for deterministic automata"""
+        return []
+
     def fill_main_c(self) -> str:
         main_c = super().fill_main_c()
 
@@ -127,5 +148,454 @@ class dot2k(Monitor, Dot2c):
         main_c = main_c.replace("%%MIN_TYPE%%", min_type)
         main_c = main_c.replace("%%NR_EVENTS%%", str(nr_events))
         main_c = main_c.replace("%%MONITOR_TYPE%%", monitor_type)
+        main_c = main_c.replace("%%MONITOR_CLASS%%", self.monitor_class)
 
         return main_c
+
+class da2k(dot2k):
+    """Deterministic automata only"""
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if self.is_hybrid_automata():
+            raise ValueError("Detected hybrid automata, use the 'ha' class")
+
+class ha2k(dot2k):
+    """Hybrid automata only"""
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if not self.is_hybrid_automata():
+            raise ValueError("Detected deterministic automata, use the 'da' class")
+        self.trace_h = self._read_template_file("trace_hybrid.h")
+        self.__parse_constraints()
+
+    def fill_monitor_class_type(self) -> str:
+        if self.monitor_type == "per_task":
+            return "HA_MON_EVENTS_ID"
+        return "HA_MON_EVENTS_IMPLICIT"
+
+    def fill_monitor_class(self) -> str:
+        """
+        Used for tracepoint classes, since they are shared we keep da
+        instead of ha (also for the ha specific tracepoints).
+        The tracepoint class is not visible to the tools.
+        """
+        return super().fill_monitor_class()
+
+    def __adjust_value(self, value: str | int, unit: str | None) -> str:
+        """Adjust the value in ns"""
+        try:
+            value = int(value)
+        except ValueError:
+            # it's a constant, a parameter or a function
+            if value.endswith("()"):
+                return value.replace("()", "(ha_mon)")
+            return value
+        match unit:
+            case "us":
+                value *= 1000
+            case "ms":
+                value *= 1000000
+            case "s":
+                value *= 1000000000
+        return str(value) + "ull"
+
+    def __parse_single_constraint(self, rule: dict, value: str) -> str:
+        return "ha_get_env(ha_mon, %s%s, time_ns) %s %s" % (
+                rule["env"], self.enum_suffix, 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.rstrip(f"_{self.name}") in self.envs
+        return env
+
+    def __start_to_invariant_check(self, constr:str) -> str:
+        # by default assume the timer has ns expiration
+        env = self.__get_constraint_env(constr)
+        clock_type = "ns"
+        if self.env_types.get(env.rstrip(f"_{self.name}")) == "j":
+            clock_type = "jiffy"
+
+        return "return ha_check_invariant_%s(ha_mon, %s, time_ns)" % (clock_type, env)
+
+    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("("):]
+
+    def __parse_timer_constraint(self, rule: dict, value: str) -> str:
+        # by default assume the timer has ns expiration
+        clock_type = "ns"
+        if self.env_types.get(rule["env"]) == "j":
+            clock_type = "jiffy"
+
+        return "ha_start_timer_%s(ha_mon, %s%s, %s, time_ns)" % (clock_type, rule["env"],
+                                                                 self.enum_suffix, value)
+
+    def __format_guard_rules(self, rules: list[str]) -> list[str]:
+        """
+        Merge guard constraints as a single C return statement.
+        If the rules include a stored env, also check its validity.
+        Break lines in a best effort way that tries to keep readability.
+        """
+        if not rules:
+            return []
+
+        invalid_checks = [f"ha_monitor_env_invalid(ha_mon, {env}{self.enum_suffix}) ||"
+                         for env in self.env_stored if any(env in rule for rule in rules)]
+        if invalid_checks and len(rules) > 1:
+            rules[0] = "(" + rules[0]
+            rules[-1] = rules[-1] + ")"
+        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 ValueError("Unrecognised event constraint (%s/%s: %s)"
+                                 % (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 ValueError("Clocks in hybrid automata always require a storage (%s)"
+                                 % rule["env"])
+        else:
+            if not rule:
+                raise ValueError("Unrecognised state constraint (%s: %s)"
+                                 % (self.states[key], constr))
+            if rule["env"] not in self.env_stored:
+                raise ValueError("State constraints always require a storage (%s)"
+                                 % rule["env"])
+            if rule["op"] not in ["<", "<="]:
+                raise ValueError("State constraints must be clock expirations like clk<N (%s)"
+                                 % 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 = "ha_reset_env(ha_mon, %s%s, time_ns)" % (reset["env"],
+                                                                 self.enum_suffix)
+                    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]
+
+    def __fill_verify_invariants_func(self) -> list[str]:
+        buff = []
+        if not self.invariants:
+            return []
+
+        buff.append(
+"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+\t\t\t\t\tenum %s curr_state, enum %s event,
+\t\t\t\t\tenum %s next_state, u64 time_ns)
+{""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+
+        _else = ""
+        for state, constr in self.invariants.items():
+            check_str = self.__start_to_invariant_check(constr)
+            buff.append("\t%sif (curr_state == %s%s)" %
+                        (_else, self.states[state], self.enum_suffix))
+            buff.append("\t\t%s;" % check_str)
+            _else = "else "
+
+        buff.append("\treturn true;\n}\n")
+        return buff
+
+    def __fill_convert_inv_guard_func(self) -> list[str]:
+        # TODO only if necessary!
+        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(
+"""static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+\t\t\t\t\tenum %s curr_state, enum %s event,
+\t\t\t\t\tenum %s next_state, u64 time_ns)
+{""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+        buff.append("\tif (curr_state == next_state)\n\t\treturn;")
+
+        _else = ""
+        for state, constr in 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:
+                continue
+            buff.append("\t%sif (curr_state == %s%s)" %
+                        (_else, self.states[state], self.enum_suffix))
+            buff.append("\t\t%s;" % self.__start_to_conv(constr))
+            _else = "else "
+
+        buff.append("}\n")
+        return buff
+
+    def __fill_verify_guards_func(self) -> list[str]:
+        buff = []
+        if not self.guards:
+            return []
+
+        buff.append(
+"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+\t\t\t\t    enum %s curr_state, enum %s event,
+\t\t\t\t    enum %s next_state, u64 time_ns)
+{
+\tbool res = true;
+""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+
+        _else = ""
+        for edge, constr in self.guards.items():
+            buff.append("\t%sif (curr_state == %s%s && event == %s%s)" %
+                        (_else, self.states[edge[0]], self.enum_suffix,
+                         self.events[edge[1]], self.enum_suffix))
+            if constr.count(";") > 0:
+                buff[-1] += " {"
+            buff += [ "\t\t%s;" % c for c in constr.split(";") ]
+            if constr.count(";") > 0:
+                _else = "} else "
+            else:
+                _else = "else "
+        if _else[0] == "}":
+            buff.append("\t}")
+        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:
+            return []
+
+        buff.append(
+"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+\t\t\t\t       enum %s curr_state, enum %s event,
+\t\t\t\t       enum %s next_state, u64 time_ns)
+{""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+
+        conditions = ["next_state == curr_state"]
+        conditions += ["event != %s%s" % (e, self.enum_suffix)
+                       for e in self.self_loop_reset_events]
+        condition_str = " && ".join(conditions)
+        buff.append(f"\tif ({condition_str})\n\t\treturn;")
+
+        _else = ""
+        for state, constr in self.invariants.items():
+            buff.append("\t%sif (next_state == %s%s)" %
+                        (_else, self.states[state], self.enum_suffix))
+            buff.append("\t\t%s;" % constr)
+            _else = "else "
+
+        for state in self.invariants:
+            buff.append("\telse if (curr_state == %s%s)" %
+                        (self.states[state], self.enum_suffix))
+            buff.append("\t\tha_cancel_timer(ha_mon);")
+
+        buff.append("}\n")
+        return buff
+
+    def __fill_constr_func(self) -> list[str]:
+        buff = []
+        if not self.constraints:
+            return []
+
+        buff.append(
+"""/*
+ * 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.
+ * 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()
+
+        buff.append(
+"""static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+\t\t\t\t enum %s curr_state, enum %s event,
+\t\t\t\t enum %s next_state, u64 time_ns)
+{""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+
+        if self.invariants:
+            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:
+            buff.append("\tif (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))\n\t\treturn false;\n")
+
+        if self.invariants:
+            buff.append("\tha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);\n")
+
+        buff.append("\treturn true;\n}\n")
+        return buff
+
+    def __fill_env_getter(self, env: str) -> str:
+        if env in self.env_types:
+            match self.env_types[env]:
+                case "ns" | "us" | "ms" | "s":
+                    return "ha_get_clk_ns(ha_mon, env, time_ns);"
+                case "j":
+                    return "ha_get_clk_jiffy(ha_mon, env);"
+        return "/* XXX: how do I read %s? */" % env
+
+    def __fill_env_resetter(self, env: str) -> str:
+        if env in self.env_types:
+            match self.env_types[env]:
+                case "ns" | "us" | "ms" | "s":
+                    return "ha_reset_clk_ns(ha_mon, env, time_ns);"
+                case "j":
+                    return "ha_reset_clk_jiffy(ha_mon, env);"
+        return "/* XXX: how do I reset %s? */" % env
+
+    def __fill_hybrid_get_reset_functions(self) -> list[str]:
+        buff = []
+        if self.is_hybrid_automata():
+            for var in self.constraint_vars:
+                if var.endswith("()"):
+                    func_name = var.replace("()", "")
+                    if func_name.isupper():
+                        buff.append("#define %s(ha_mon) /* XXX: what is %s(ha_mon)? */\n" % (func_name, func_name))
+                    else:
+                        buff.append("static inline u64 %s(struct ha_monitor *ha_mon)\n{" % func_name)
+                        buff.append("\treturn /* XXX: what is %s(ha_mon)? */;" % func_name)
+                        buff.append("}\n")
+                elif var.isupper():
+                    buff.append("#define %s /* XXX: what is %s? */\n" % (var, var))
+                else:
+                    buff.append("static u64 %s = /* XXX: default value */;" % var)
+                    buff.append("module_param(%s, ullong, 0644);\n" % var)
+            buff.append("""/*
+ * 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.
+ */""")
+            buff.append("static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs%s env, u64 time_ns)\n{" % self.enum_suffix)
+            _else = ""
+            for env in self.envs:
+                buff.append("\t%sif (env == %s%s)\n\t\treturn %s" %
+                            (_else, env, self.enum_suffix, self.__fill_env_getter(env)))
+                _else = "else "
+            buff.append("\treturn ENV_INVALID_VALUE;\n}\n")
+            if len(self.env_stored):
+                buff.append("static void ha_reset_env(struct ha_monitor *ha_mon, enum envs%s env, u64 time_ns)\n{" % self.enum_suffix)
+                _else = ""
+                for env in self.env_stored:
+                    buff.append("\t%sif (env == %s%s)\n\t\t%s" %
+                                (_else, env, self.enum_suffix, self.__fill_env_resetter(env)))
+                    _else = "else "
+                buff.append("}\n")
+        return buff
+
+    def _fill_hybrid_definitions(self) -> list[str]:
+        return self.__fill_hybrid_get_reset_functions() + self.__fill_constr_func()
+
+    def _fill_timer_type(self) -> list:
+        if self.invariants:
+            return [
+                    "/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */",
+                    "#define HA_TIMER_TYPE HA_TIMER_HRTIMER"
+                    ]
+        return []
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 3441385c1177..b80af3fd6701 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -255,12 +255,14 @@ class Monitor(RVGenerator):
         monitor_class_type = self.fill_monitor_class_type()
         tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
         tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
+        tracepoint_args_skel_error_env = self.fill_tracepoint_args_skel("error_env")
         trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
         trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
         trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
         trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
         trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
         trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
+        trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%", tracepoint_args_skel_error_env)
         return trace_h
 
     def print_files(self):
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index a14e4f0883db..bf0999f6657a 100644
--- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -21,7 +21,7 @@
  */
 #define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
 #include "%%MODEL_NAME%%.h"
-#include <rv/da_monitor.h>
+#include <rv/%%MONITOR_CLASS%%_monitor.h>
 
 /*
  * This is the instrumentation part of the monitor.
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
new file mode 100644
index 000000000000..c8290e9ba2f4
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_%%MODEL_NAME_UP%%
+DEFINE_EVENT(event_%%MONITOR_CLASS%%, event_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_EVENT%%);
+
+DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR%%);
+
+DEFINE_EVENT(error_env_%%MONITOR_CLASS%%, error_env_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%);
+#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */
-- 
2.51.0


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

* [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (11 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-10 13:46   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 14/20] rv: Add sample hybrid monitors stall Gabriele Monaco
                   ` (7 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
	linux-doc
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Describe theory and implementation of hybrid automata in the dedicated
page hybrid_automata.rst
Include a section on how to integrate a hybrid automaton in
monitor_synthesis.rst
Also remove a hanging $ in deterministic_automata.rst

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 .../trace/rv/deterministic_automata.rst       |   2 +-
 Documentation/trace/rv/hybrid_automata.rst    | 340 ++++++++++++++++++
 Documentation/trace/rv/index.rst              |   1 +
 Documentation/trace/rv/monitor_synthesis.rst  | 107 ++++++
 4 files changed, 449 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/trace/rv/hybrid_automata.rst

diff --git a/Documentation/trace/rv/deterministic_automata.rst b/Documentation/trace/rv/deterministic_automata.rst
index d0638f95a455..7a1c2b20ec72 100644
--- a/Documentation/trace/rv/deterministic_automata.rst
+++ b/Documentation/trace/rv/deterministic_automata.rst
@@ -11,7 +11,7 @@ where:
 - *E* is the finite set of events;
 - x\ :subscript:`0` is the initial state;
 - X\ :subscript:`m` (subset of *X*) is the set of marked (or final) states.
-- *f* : *X* x *E* -> *X* $ is the transition function. It defines the state
+- *f* : *X* x *E* -> *X* is the transition function. It defines the state
   transition in the occurrence of an event from *E* in the state *X*. In the
   special case of deterministic automata, the occurrence of the event in *E*
   in a state in *X* has a deterministic next state from *X*.
diff --git a/Documentation/trace/rv/hybrid_automata.rst b/Documentation/trace/rv/hybrid_automata.rst
new file mode 100644
index 000000000000..ea701114c54d
--- /dev/null
+++ b/Documentation/trace/rv/hybrid_automata.rst
@@ -0,0 +1,340 @@
+Hybrid Automata
+===============
+
+Hybrid automata are an extension of deterministic automata, there are several
+definitions of hybrid automata in the literature. The adaptation implemented
+here is formally denoted by G and defined as a 7-tuple:
+
+        *G* = { *X*, *E*, *V*, *f*, x\ :subscript:`0`, X\ :subscript:`m`, *i* }
+
+- *X* is the set of states;
+- *E* is the finite set of events;
+- *V* is the finite set of environment variables;
+- x\ :subscript:`0` is the initial state;
+- X\ :subscript:`m` (subset of *X*) is the set of marked (or final) states.
+- *f* : *X* x *E* x *C(V)* -> *X* is the transition function.
+  It defines the state transition in the occurrence of an event from *E* in the
+  state *X*. Unlike deterministic automata, the transition function also
+  includes guards from the set of all possible constraints (defined as *C(V)*).
+  Guards can be true or false with the valuation of *V* when the event occurs,
+  and the transition is possible only when constraints are true. Similarly to
+  deterministic automata, the occurrence of the event in *E* in a state in *X*
+  has a deterministic next state from *X*, if the guard is true.
+- *i* : *X* -> *C'(V)* is the invariant assignment function, this is a
+  constraint assigned to each state in *X*, every state in *X* must be left
+  before the invariant turns to false. We can omit the representation of
+  invariants whose value is true regardless of the valuation of *V*.
+
+The set of all possible constraints *C(V)* is defined according to the
+following grammar:
+
+        g = v < c | v > c | v <= c | v >= c | v == c | v != c | g && g | true
+
+With v a variable in *V* and c a numerical value.
+
+We define the special case of hybrid automata whose variables grow with uniform
+rates as timed automata. In this case, the variables are called clocks.
+As the name implies, timed automata can be used to describe real time.
+Additionally, clocks support another type of guard which always evaluates to true:
+
+        reset(v)
+
+The reset constraint is used to set the value of a clock to 0.
+
+The set of invariant constraints *C'(V)* is a subset of *C(V)* including only
+constraint of the form:
+
+        g = v < c | true
+
+This simplifies the implementation as a clock expiration is a necessary and
+sufficient condition for the violation of invariants while still allowing more
+complex constraints to be specified as guards.
+
+It is important to note that any valid hybrid automaton is a valid
+deterministic automaton with additional guards and invariants. Those can only
+further constrain what transitions are valid but it is not possible to define
+transition functions starting from the same state in *X* and the same event in
+*E* but ending up in different states in *X* based on the valuation of *V*.
+
+Examples
+--------
+
+Wip as hybrid automaton
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The 'wip' (wakeup in preemptive) example introduced as a deterministic automaton
+can also be described as:
+
+- *X* = { ``any_thread_running`` }
+- *E* = { ``sched_waking`` }
+- *V* = { ``preemptive`` }
+- x\ :subscript:`0` = ``any_thread_running``
+- X\ :subscript:`m` = {``any_thread_running``}
+- *f* =
+   - *f*\ (``any_thread_running``, ``sched_waking``, ``preemptive==0``) = ``any_thread_running``
+- *i* =
+   - *i*\ (``any_thread_running``) = ``true``
+
+Which can be represented graphically as::
+
+     |
+     |
+     v
+   #====================#   sched_waking;preemptive==0
+   H                    H ------------------------------+
+   H any_thread_running H                               |
+   H                    H <-----------------------------+
+   #====================#
+
+In this example, by using the preemptive state of the system as an environment
+variable, we can assert this constraint on ``sched_waking`` without requiring
+preemption events (as we would in a deterministic automaton), which can be
+useful in case those events are not available or not reliable on the system.
+
+Since all the invariants in *i* are true, we can omit them from the representation.
+
+Stall model with guards (iteration 1)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As a sample timed automaton we can define 'stall' as:
+
+- *X* = { ``dequeued``, ``enqueued``, ``running``}
+- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
+- *V* = { ``clk`` }
+- x\ :subscript:`0` = ``dequeue``
+- X\ :subscript:`m` = {``dequeue``}
+- *f* =
+   - *f*\ (``enqueued``, ``switch_in``, ``clk < threshold``) = ``running``
+   - *f*\ (``running``, ``dequeue``) = ``dequeued``
+   - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
+- *i* = *omitted as all true*
+
+Graphically represented as::
+
+       |
+       |
+       v
+     #============================#
+     H          dequeued          H <+
+     #============================#  |
+       |                             |
+       | enqueue; reset(clk)         |
+       v                             |
+     +----------------------------+  |
+     |          enqueued          |  | dequeue
+     +----------------------------+  |
+       |                             |
+       | switch_in; clk < threshold  |
+       v                             |
+     +----------------------------+  |
+     |          running           | -+
+     +----------------------------+
+
+This model imposes that the time between when a task is enqueued (it becomes
+runnable) and when the task gets to run must be lower than a certain threshold.
+A failure in this model means that the task is starving.
+One problem in using guards on the edges in this case is that the model will
+not report a failure until the ``switch_in`` event occurs. This means that,
+according to the model, it is valid for the task never to run.
+
+Stall model with invariants (iteration 2)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first iteration isn't exactly what was intended, we can change the model as:
+
+- *X* = { ``dequeued``, ``enqueued``, ``running``}
+- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
+- *V* = { ``clk`` }
+- x\ :subscript:`0` = ``dequeue``
+- X\ :subscript:`m` = {``dequeue``}
+- *f* =
+   - *f*\ (``enqueued``, ``switch_in``) = ``running``
+   - *f*\ (``running``, ``dequeue``) = ``dequeued``
+   - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
+- *i* =
+   - *i*\ (``enqueued``) = ``clk < threshold``
+
+Graphically::
+
+    |
+    |
+    v
+  #=========================#
+  H        dequeued         H <+
+  #=========================#  |
+    |                          |
+    | enqueue; reset(clk)      |
+    v                          |
+  +-------------------------+  |
+  |        enqueued         |  |
+  |    clk < threshold      |  | dequeue
+  +-------------------------+  |
+    |                          |
+    | switch_in                |
+    v                          |
+  +-------------------------+  |
+  |         running         | -+
+  +-------------------------+
+
+In this case, we moved the guard as an invariant to the ``enqueued`` state,
+this means we not only forbid the occurrence of ``switch_in`` when ``clk`` is
+past the threshold but also mark as invalid in case we are *still* in
+``enqueued`` after the threshold. This model is effectively in an invalid state
+as soon as a task is starving, rather than when the starving task finally runs.
+
+Hybrid Automaton in C
+---------------------
+
+The definition of hybrid automata in C is heavily based on the deterministic
+automata one. Specifically, we add the set of environment variables and the
+constraints (both guards on transitions and invariants on states) as follows.
+This is a combination of both iterations of the stall example::
+
+  /* enum representation of X (set of states) to be used as index */
+  enum states {
+	dequeued = 0,
+	enqueued,
+	running,
+	state_max
+  };
+
+  #define INVALID_STATE state_max
+
+  /* enum representation of E (set of events) to be used as index */
+  enum events {
+	dequeue = 0,
+	enqueue,
+	switch_in,
+	event_max
+  };
+
+  /* enum representation of V (set of environment variables) to be used as index */
+  enum envs {
+	clk = 0,
+	env_max,
+	env_max_stored = env_max
+  };
+
+  struct automaton {
+	char *state_names[state_max];                  // X: the set of states
+	char *event_names[event_max];                  // E: the finite set of events
+	char *env_names[env_max];                      // V: the finite set of env vars
+	unsigned char function[state_max][event_max];  // f: transition function
+	unsigned char initial_state;                   // x_0: the initial state
+	bool final_states[state_max];                  // X_m: the set of marked states
+  };
+
+  struct automaton aut = {
+	.state_names = {
+		"dequeued",
+		"enqueued",
+		"running"
+	},
+	.event_names = {
+		"dequeue",
+		"enqueue",
+		"switch_in"
+	},
+	.env_names = {
+		"clk"
+	},
+	.function = {
+		{ INVALID_STATE,      enqueued, INVALID_STATE },
+		{ INVALID_STATE, INVALID_STATE,       running },
+		{      dequeued, INVALID_STATE, INVALID_STATE },
+	},
+	.initial_state = dequeued,
+	.final_states = { 1, 0, 0 },
+  };
+
+  static bool verify_constraint(enum states curr_state, enum events event,
+                                enum states next_state)
+  {
+	bool res = true;
+
+	/* Validate guards as part of f */
+	if (curr_state == enqueued && event == sched_switch_in)
+		res = get_env(clk) < threshold;
+	else if (curr_state == dequeued && event == sched_wakeup)
+		reset_env(clk);
+
+	/* Validate invariants in i */
+    if (next_state == curr_state || !res)
+		return res;
+	if (next_state == enqueued)
+		ha_start_timer_jiffy(ha_mon, clk, threshold_jiffies);
+	else if (curr_state == enqueued)
+		res = !ha_cancel_timer(ha_mon);
+	return res;
+  }
+
+The function ``verify_constraint``, here reported as simplified, checks guards,
+performs resets and starts timers to validate invariants according to
+specification.
+Due to the complex nature of environment variables, the user needs to provide
+functions to get and reset environment variables, although we provide some
+helpers for common types (e.g. clocks with ns or jiffy granularity).
+Since invariants are only defined as clock expirations (e.g. *clk <
+threshold*), the callback for timers armed when entering the state is in fact a
+failure in the model and triggers a reaction. Leaving the state stops the timer
+and checks for its expiration, in case the callback was late.
+
+It is important to note that timers introduce overhead, if the monitor has
+several instances (e.g. all tasks) this can become an issue.
+If the monitor is guaranteed to *eventually* leave the state and the incurred
+delay to wait for the next event is acceptable, guards can be use to lower the
+monitor's overhead.
+For instance in the stall example, if we are only interested in reporting
+stalled tasks, rather than reacting as soon as a task is stalled, and
+``switch_in`` is eventually going to occur, we can use the first iteration.
+
+Graphviz .dot format
+--------------------
+
+Also the Graphviz representation of hybrid automata is an extension of the
+deterministic automata one. Specifically, guards can be provided in the event
+name separated by ``;``::
+
+    "state_start" -> "state_dest" [ label = "sched_waking;preemptible==0;reset(clk)" ];
+
+Invariant can be specified in the state label (not the node name!) separated by ``\n``::
+
+    "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+
+Constraints can be specified as valid C comparisons and allow spaces, the first
+element of the comparison must be the clock while the second is a numerical or
+parametrised value. Guards allow comparisons to be combined with boolean
+operations (``&&`` and ``||``), resets must be separated from other constraints.
+
+This is the full example of the last version of the 'stall' model in DOT::
+
+  digraph state_automaton {
+      {node [shape = circle] "enqueued"};
+      {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+      {node [shape = doublecircle] "dequeued"};
+      {node [shape = circle] "running"};
+      "__init_dequeued" -> "dequeued";
+      "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+      "running" [label = "running"];
+      "dequeued" [label = "dequeued"];
+      "enqueued" -> "running" [ label = "switch_in" ];
+      "running" -> "dequeued" [ label = "dequeue" ];
+      "dequeued" -> "enqueued" [ label = "enqueue;reset(clk)" ];
+      { rank = min ;
+          "__init_dequeued";
+          "dequeued";
+      }
+  }
+
+References
+----------
+
+One book covering model checking and timed automata is::
+
+  Christel Baier and Joost-Pieter Katoen: Principles of Model Checking,
+  The MIT Press, 2008.
+
+Hybrid automata are described in detail in::
+
+  Thomas Henzinger: The theory of hybrid automata,
+  Proceedings 11th Annual IEEE Symposium on Logic in Computer Science, 1996.
diff --git a/Documentation/trace/rv/index.rst b/Documentation/trace/rv/index.rst
index a2812ac5cfeb..ad298784bda2 100644
--- a/Documentation/trace/rv/index.rst
+++ b/Documentation/trace/rv/index.rst
@@ -9,6 +9,7 @@ Runtime Verification
    runtime-verification.rst
    deterministic_automata.rst
    linear_temporal_logic.rst
+   hybrid_automata.rst
    monitor_synthesis.rst
    da_monitor_instrumentation.rst
    monitor_wip.rst
diff --git a/Documentation/trace/rv/monitor_synthesis.rst b/Documentation/trace/rv/monitor_synthesis.rst
index ce0c1a5104d4..00d3abd8c05a 100644
--- a/Documentation/trace/rv/monitor_synthesis.rst
+++ b/Documentation/trace/rv/monitor_synthesis.rst
@@ -45,6 +45,7 @@ creating monitors. The header files are:
 
   * rv/da_monitor.h for deterministic automaton monitor.
   * rv/ltl_monitor.h for linear temporal logic monitor.
+  * rv/ha_monitor.h for hybrid automaton monitor.
 
 rvgen
 -----
@@ -252,6 +253,112 @@ the task, the monitor may need some time to start validating tasks which have
 been running before the monitor is enabled. Therefore, it is recommended to
 start the tasks of interest after enabling the monitor.
 
+rv/ha_monitor.h
++++++++++++++++
+
+The implementation of hybrid automaton monitors derives directly from the
+deterministic automaton one. Despite using a different header
+(``ha_monitor.h``) the functions to handle events are the same (e.g.
+``da_handle_event``).
+
+Additionally, the `rvgen` tool populates skeletons for the
+``ha_verify_constraint``, ``ha_get_env`` and ``ha_reset_env`` based on the
+monitor specification in the monitor source file.
+
+``ha_verify_constraint`` is typically ready as it is generated by `rvgen`:
+
+* standard constraints on edges are turned into the form::
+
+    res = ha_get_env(ha_mon, ENV) < VALUE;
+
+* reset constraints are turned into the form::
+
+    ha_reset_env(ha_mon, ENV);
+
+* constraints on the state are implemented using timers
+
+  - armed before entering the state
+
+  - cancelled while entering any other state
+
+  - untouched if the state does not change as a result of the event
+
+  - checked if the timer expired but the callback did not run
+
+  - available implementation are `HA_TIMER_HRTIMER` and `HA_TIMER_WHEEL`
+
+    - hrtimers are more precise but may have higher overhead
+
+    - select by defining `HA_TIMER_TYPE` before including the header::
+
+      #define HA_TIMER_TYPE HA_TIMER_HRTIMER
+
+Constraint values can be specified in different forms:
+
+* literal value (with optional unit). E.g.::
+
+    preemptive == 0
+    clk < 100ns
+    threshold <= 10j
+
+* constant value (uppercase string). E.g.::
+
+    clk < MAX_NS
+
+* parameter (lowercase string). E.g.::
+
+    clk <= threshold_jiffies
+
+* macro (uppercase string with parentheses). E.g.::
+
+    clk < MAX_NS()
+
+* function (lowercase string with parentheses). E.g.::
+
+    clk <= threshold_jiffies()
+
+In all cases, `rvgen` will try to understand the type of the environment
+variable from the name or unit. For instance, constants or parameters
+terminating with ``_NS`` or ``_jiffies`` are intended as clocks with ns and jiffy
+granularity, respectively. Literals with measure unit `j` are jiffies and if a
+time unit is specified (`ns` to `s`), `rvgen` will convert the value to `ns`.
+
+Constants need to be defined by the user (but unlike the name, they don't
+necessarily need to be defined as constants). Parameters get converted to
+module parameters and the user needs to provide a default value.
+Also function and macros are defined by the user, by default they get as an
+argument the ``ha_monitor``, a common usage would be to get the required value
+from the target, e.g. the task in per-task monitors, using the helper
+``ha_get_target(ha_mon)``.
+
+If `rvgen` determines that the variable is a clock, it provides the getter and
+resetter based on the unit. Otherwise, the user needs to provide an appropriate
+definition.
+Typically non-clock environment variables are not reset. In such case only the
+getter skeleton will be present in the file generated by `rvgen`.
+For instance, the getter for preemptive can be filled as::
+
+  static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env)
+  {
+      if (env == preemptible)
+          return preempt_count() == 0;
+      return ENV_INVALID_VALUE;
+  }
+
+The function is supplied the ``ha_mon`` parameter in case some storage is
+required (as it is for clocks), but environment variables without reset do not
+require a storage and can ignore that argument.
+The number of environment variables requiring a storage is limited by
+``MAX_HA_ENV_LEN``, however such limitation doesn't stand for other variables.
+
+Finally, constraints on states are only valid for clocks and only if the
+constraint is of the form `clk < N`. This is because such constraints are
+implemented with the expiration of a timer.
+Typically the clock variables are reset just before arming the timer, but this
+doesn't have to be the case and the available functions take care of it.
+It is a responsibility of per-task monitors to make sure no timer is left
+running when the task exits.
+
 Final remarks
 -------------
 
-- 
2.51.0


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

* [PATCH v2 14/20] rv: Add sample hybrid monitors stall
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (12 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-10 14:23   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
                   ` (6 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Jonathan Corbet, Steven Rostedt, Masami Hiramatsu,
	linux-doc, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Add a sample monitor to showcase hybrid/timed automata.
The stall monitor identifies tasks stalled for longer than a threshold
and reacts when that happens.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 Documentation/tools/rv/index.rst             |   1 +
 Documentation/tools/rv/rv-mon-stall.rst      |  44 ++++++
 Documentation/trace/rv/index.rst             |   1 +
 Documentation/trace/rv/monitor_stall.rst     |  43 ++++++
 kernel/trace/rv/Kconfig                      |   1 +
 kernel/trace/rv/Makefile                     |   1 +
 kernel/trace/rv/monitors/stall/Kconfig       |  13 ++
 kernel/trace/rv/monitors/stall/stall.c       | 146 +++++++++++++++++++
 kernel/trace/rv/monitors/stall/stall.h       |  64 ++++++++
 kernel/trace/rv/monitors/stall/stall_trace.h |  19 +++
 kernel/trace/rv/rv_trace.h                   |   1 +
 tools/verification/models/stall.dot          |  20 +++
 12 files changed, 354 insertions(+)
 create mode 100644 Documentation/tools/rv/rv-mon-stall.rst
 create mode 100644 Documentation/trace/rv/monitor_stall.rst
 create mode 100644 kernel/trace/rv/monitors/stall/Kconfig
 create mode 100644 kernel/trace/rv/monitors/stall/stall.c
 create mode 100644 kernel/trace/rv/monitors/stall/stall.h
 create mode 100644 kernel/trace/rv/monitors/stall/stall_trace.h
 create mode 100644 tools/verification/models/stall.dot

diff --git a/Documentation/tools/rv/index.rst b/Documentation/tools/rv/index.rst
index 64ba2efe2e85..4d66e0a78e1e 100644
--- a/Documentation/tools/rv/index.rst
+++ b/Documentation/tools/rv/index.rst
@@ -16,6 +16,7 @@ Runtime verification (rv) tool
    rv-mon-wip
    rv-mon-wwnr
    rv-mon-sched
+   rv-mon-stall
 
 .. only::  subproject and html
 
diff --git a/Documentation/tools/rv/rv-mon-stall.rst b/Documentation/tools/rv/rv-mon-stall.rst
new file mode 100644
index 000000000000..c79d7c2e4dd4
--- /dev/null
+++ b/Documentation/tools/rv/rv-mon-stall.rst
@@ -0,0 +1,44 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+============
+rv-mon-stall
+============
+--------------------
+Stalled task monitor
+--------------------
+
+:Manual section: 1
+
+SYNOPSIS
+========
+
+**rv mon stall** [*OPTIONS*]
+
+DESCRIPTION
+===========
+
+The stalled task (**stall**) monitor is a sample per-task timed monitor that
+checks if tasks are scheduled within a defined threshold after they are ready.
+
+See kernel documentation for further information about this monitor:
+<https://docs.kernel.org/trace/rv/monitor_stall.html>
+
+OPTIONS
+=======
+
+.. include:: common_ikm.rst
+
+SEE ALSO
+========
+
+**rv**\(1), **rv-mon**\(1)
+
+Linux kernel *RV* documentation:
+<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
+
+AUTHOR
+======
+
+Written by Gabriele Monaco <gmonaco@redhat.com>
+
+.. include:: common_appendix.rst
diff --git a/Documentation/trace/rv/index.rst b/Documentation/trace/rv/index.rst
index ad298784bda2..bf9962f49959 100644
--- a/Documentation/trace/rv/index.rst
+++ b/Documentation/trace/rv/index.rst
@@ -16,3 +16,4 @@ Runtime Verification
    monitor_wwnr.rst
    monitor_sched.rst
    monitor_rtapp.rst
+   monitor_stall.rst
diff --git a/Documentation/trace/rv/monitor_stall.rst b/Documentation/trace/rv/monitor_stall.rst
new file mode 100644
index 000000000000..e4d9b050a32f
--- /dev/null
+++ b/Documentation/trace/rv/monitor_stall.rst
@@ -0,0 +1,43 @@
+Monitor stall
+=============
+
+- Name: stall - wakeup in preemptive
+- Type: per-task hybrid automaton
+- Author: Gabriele Monaco <gmonaco@redhat.com>
+
+Description
+-----------
+
+The stalled task (stall) monitor is a sample per-task timed monitor that checks
+if tasks are scheduled within a defined threshold after they are ready::
+
+                        |
+                        |
+                        v
+                      #==================================#
+                      H             dequeued             H <+
+                      #==================================#  |
+                        |                                   |
+                        | sched_wakeup;reset(clk)           |
+                        v                                   |
+                      +----------------------------------+  |
+                      |             enqueued             |  |
+                      |     clk < threshold_jiffies      |  | sched_switch_wait
+                      +----------------------------------+  |
+                        |                                   |
+                        | sched_switch_in                   |
+    sched_switch_in     v                                   |
+    sched_wakeup      +----------------------------------+  |
+  +------------------ |                                  |  |
+  |                   |             running              |  |
+  +-----------------> |                                  | -+
+                      +----------------------------------+
+
+
+The threshold can be configured as a parameter by either booting with the
+``stall.threshold_jiffies=<new value>`` argument or writing a new value to
+``/sys/module/stall/parameters/threshold_jiffies``.
+
+Specification
+-------------
+Grapviz Dot file in tools/verification/models/stall.dot
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 4ad392dfc57f..720fbe4935f8 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -78,6 +78,7 @@ source "kernel/trace/rv/monitors/pagefault/Kconfig"
 source "kernel/trace/rv/monitors/sleep/Kconfig"
 # Add new rtapp monitors here
 
+source "kernel/trace/rv/monitors/stall/Kconfig"
 # Add new monitors here
 
 config RV_REACTORS
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 750e4ad6fa0f..51c95e2d2da6 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_RV_MON_STS) += monitors/sts/sts.o
 obj-$(CONFIG_RV_MON_NRP) += monitors/nrp/nrp.o
 obj-$(CONFIG_RV_MON_SSSW) += monitors/sssw/sssw.o
 obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
+obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
 # Add new monitors here
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
diff --git a/kernel/trace/rv/monitors/stall/Kconfig b/kernel/trace/rv/monitors/stall/Kconfig
new file mode 100644
index 000000000000..6f846b642544
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_STALL
+	depends on RV
+	select HA_MON_EVENTS_ID
+	bool "stall monitor"
+	help
+	  Enable the stall sample monitor that illustrates the usage of hybrid
+	  automata monitors. It can be used to identify tasks stalled for
+	  longer than a threshold.
+
+	  For further information, see:
+	    Documentation/trace/rv/monitor_stall.rst
diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c
new file mode 100644
index 000000000000..20e1345601a4
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/stall.c
@@ -0,0 +1,146 @@
+// 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 "stall"
+
+#include <trace/events/sched.h>
+#include <rv_trace.h>
+
+#define RV_MON_TYPE RV_MON_PER_TASK
+#define HA_TIMER_TYPE HA_TIMER_WHEEL
+#include "stall.h"
+#include <rv/ha_monitor.h>
+
+static u64 threshold_jiffies = 1000;
+module_param(threshold_jiffies, ullong, 0644);
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_stall env, u64 time_ns)
+{
+	if (env == clk_stall)
+		return ha_get_clk_jiffy(ha_mon, env);
+	return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_stall env, u64 time_ns)
+{
+	if (env == clk_stall)
+		ha_reset_clk_jiffy(ha_mon, env);
+}
+
+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 == enqueued_stall)
+		return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns);
+	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 == dequeued_stall && event == sched_wakeup_stall)
+		ha_reset_env(ha_mon, clk_stall, 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)
+		return;
+	if (next_state == enqueued_stall)
+		ha_start_timer_jiffy(ha_mon, clk_stall, threshold_jiffies, time_ns);
+	else if (curr_state == enqueued_stall)
+		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_sched_switch(void *data, bool preempt,
+				struct task_struct *prev,
+				struct task_struct *next,
+				unsigned int prev_state)
+{
+	if (!preempt && prev_state != TASK_RUNNING)
+		da_handle_start_event(prev, sched_switch_wait_stall);
+	da_handle_event(next, sched_switch_in_stall);
+}
+
+static void handle_sched_wakeup(void *data, struct task_struct *p)
+{
+	da_handle_event(p, sched_wakeup_stall);
+}
+
+static int enable_stall(void)
+{
+	int retval;
+
+	retval = da_monitor_init();
+	if (retval)
+		return retval;
+
+	rv_attach_trace_probe("stall", sched_switch, handle_sched_switch);
+	rv_attach_trace_probe("stall", sched_wakeup, handle_sched_wakeup);
+
+	return 0;
+}
+
+static void disable_stall(void)
+{
+	rv_this.enabled = 0;
+
+	rv_detach_trace_probe("stall", sched_switch, handle_sched_switch);
+	rv_detach_trace_probe("stall", sched_wakeup, handle_sched_wakeup);
+
+	da_monitor_destroy();
+}
+
+static struct rv_monitor rv_this = {
+	.name = "stall",
+	.description = "identify tasks stalled for longer than a threshold.",
+	.enable = enable_stall,
+	.disable = disable_stall,
+	.reset = da_monitor_reset_all,
+	.enabled = 0,
+};
+
+static int __init register_stall(void)
+{
+	return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_stall(void)
+{
+	rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_stall);
+module_exit(unregister_stall);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("stall: identify tasks stalled for longer than a threshold.");
diff --git a/kernel/trace/rv/monitors/stall/stall.h b/kernel/trace/rv/monitors/stall/stall.h
new file mode 100644
index 000000000000..434b0c623fc4
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/stall.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of stall automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME stall
+
+enum states_stall {
+	dequeued_stall,
+	enqueued_stall,
+	running_stall,
+	state_max_stall,
+};
+
+#define INVALID_STATE state_max_stall
+
+enum events_stall {
+	sched_switch_in_stall,
+	sched_switch_wait_stall,
+	sched_wakeup_stall,
+	event_max_stall,
+};
+
+enum envs_stall {
+	clk_stall,
+	env_max_stall,
+	env_max_stored_stall = env_max_stall,
+};
+
+_Static_assert(env_max_stored_stall <= MAX_HA_ENV_LEN, "Not enough slots");
+
+struct automaton_stall {
+	char *state_names[state_max_stall];
+	char *event_names[event_max_stall];
+	char *env_names[env_max_stall];
+	unsigned char function[state_max_stall][event_max_stall];
+	unsigned char initial_state;
+	bool final_states[state_max_stall];
+};
+
+static const struct automaton_stall automaton_stall = {
+	.state_names = {
+		"dequeued",
+		"enqueued",
+		"running",
+	},
+	.event_names = {
+		"sched_switch_in",
+		"sched_switch_wait",
+		"sched_wakeup",
+	},
+	.env_names = {
+		"clk",
+	},
+	.function = {
+		{       INVALID_STATE,       INVALID_STATE,      enqueued_stall },
+		{       running_stall,       INVALID_STATE,       INVALID_STATE },
+		{       running_stall,      dequeued_stall,       running_stall },
+	},
+	.initial_state = dequeued_stall,
+	.final_states = { 1, 0, 0 },
+};
diff --git a/kernel/trace/rv/monitors/stall/stall_trace.h b/kernel/trace/rv/monitors/stall/stall_trace.h
new file mode 100644
index 000000000000..6a7cc1b1d040
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/stall_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_STALL
+DEFINE_EVENT(event_da_monitor_id, event_stall,
+	     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_stall,
+	     TP_PROTO(int id, char *state, char *event),
+	     TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_stall,
+	     TP_PROTO(int id, char *state, char *event, char *env),
+	     TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_STALL */
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 7c598967bc0e..1661f8fe4a88 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -187,6 +187,7 @@ DECLARE_EVENT_CLASS(error_env_da_monitor_id,
 		__get_str(env))
 );
 
+#include <monitors/stall/stall_trace.h>
 // Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
 
 #endif
diff --git a/tools/verification/models/stall.dot b/tools/verification/models/stall.dot
new file mode 100644
index 000000000000..98e3ae47e104
--- /dev/null
+++ b/tools/verification/models/stall.dot
@@ -0,0 +1,20 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = circle] "enqueued"};
+	{node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+	{node [shape = doublecircle] "dequeued"};
+	{node [shape = circle] "running"};
+	"__init_dequeued" -> "dequeued";
+	"enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+	"running" [label = "running"];
+	"dequeued" [label = "dequeued", color = green3];
+	"running" -> "running" [ label = "sched_switch_in\nsched_wakeup" ];
+	"enqueued" -> "running" [ label = "sched_switch_in" ];
+	"running" -> "dequeued" [ label = "sched_switch_wait" ];
+	"dequeued" -> "enqueued" [ label = "sched_wakeup;reset(clk)" ];
+	{ rank = min ;
+		"__init_dequeued";
+		"dequeued";
+	}
+}
-- 
2.51.0


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

* [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (13 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 14/20] rv: Add sample hybrid monitors stall Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-10 14:29   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 16/20] sched: Export hidden tracepoints to modules Gabriele Monaco
                   ` (5 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Jonathan Corbet, Masami Hiramatsu,
	linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The opid monitor validates that wakeup and need_resched events only
occur with interrupts and preemption disabled by following the
preemptirq tracepoints.
As reported in [1], those tracepoints might be inaccurate in some
situations (e.g. NMIs).

Since the monitor doesn't validate other ordering properties, remove the
dependency on preemptirq tracepoints and convert the monitor to a hybrid
automaton to validate the constraint during event handling.
This makes the monitor more robust by also removing the workaround for
interrupts missing the preemption tracepoints, which was working on
PREEMPT_RT only and allows the monitor to be built on kernels without
the preemptirqs tracepoints.

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

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 Documentation/trace/rv/monitor_sched.rst   |  62 +++---------
 kernel/trace/rv/monitors/opid/Kconfig      |  11 +-
 kernel/trace/rv/monitors/opid/opid.c       | 111 +++++++--------------
 kernel/trace/rv/monitors/opid/opid.h       |  86 ++++------------
 kernel/trace/rv/monitors/opid/opid_trace.h |   4 +
 kernel/trace/rv/rv_trace.h                 |   2 +-
 tools/verification/models/sched/opid.dot   |  36 ++-----
 7 files changed, 82 insertions(+), 230 deletions(-)

diff --git a/Documentation/trace/rv/monitor_sched.rst b/Documentation/trace/rv/monitor_sched.rst
index 3f8381ad9ec7..0b96d6e147c6 100644
--- a/Documentation/trace/rv/monitor_sched.rst
+++ b/Documentation/trace/rv/monitor_sched.rst
@@ -346,55 +346,21 @@ Monitor opid
 
 The operations with preemption and irq disabled (opid) monitor ensures
 operations like ``wakeup`` and ``need_resched`` occur with interrupts and
-preemption disabled or during interrupt context, in such case preemption may
-not be disabled explicitly.
+preemption disabled.
 ``need_resched`` can be set by some RCU internals functions, in which case it
-doesn't match a task wakeup and might occur with only interrupts disabled::
-
-                 |                     sched_need_resched
-                 |                     sched_waking
-                 |                     irq_entry
-                 |                   +--------------------+
-                 v                   v                    |
-               +------------------------------------------------------+
-  +----------- |                     disabled                         | <+
-  |            +------------------------------------------------------+  |
-  |              |                 ^                                     |
-  |              |          preempt_disable      sched_need_resched      |
-  |       preempt_enable           |           +--------------------+    |
-  |              v                 |           v                    |    |
-  |            +------------------------------------------------------+  |
-  |            |                   irq_disabled                       |  |
-  |            +------------------------------------------------------+  |
-  |                              |             |        ^                |
-  |     irq_entry            irq_entry         |        |                |
-  |     sched_need_resched       v             |   irq_disable           |
-  |     sched_waking +--------------+          |        |                |
-  |           +----- |              |     irq_enable    |                |
-  |           |      |    in_irq    |          |        |                |
-  |           +----> |              |          |        |                |
-  |                  +--------------+          |        |          irq_disable
-  |                     |                      |        |                |
-  | irq_enable          | irq_enable           |        |                |
-  |                     v                      v        |                |
-  |            #======================================================#  |
-  |            H                     enabled                          H  |
-  |            #======================================================#  |
-  |              |                   ^         ^ preempt_enable     |    |
-  |       preempt_disable     preempt_enable   +--------------------+    |
-  |              v                   |                                   |
-  |            +------------------+  |                                   |
-  +----------> | preempt_disabled | -+                                   |
-               +------------------+                                      |
-                 |                                                       |
-                 +-------------------------------------------------------+
-
-This monitor is designed to work on ``PREEMPT_RT`` kernels, the special case of
-events occurring in interrupt context is a shortcut to identify valid scenarios
-where the preemption tracepoints might not be visible, during interrupts
-preemption is always disabled. On non- ``PREEMPT_RT`` kernels, the interrupts
-might invoke a softirq to set ``need_resched`` and wake up a task. This is
-another special case that is currently not supported by the monitor.
+doesn't match a task wakeup and might occur with only interrupts disabled.
+The interrupt and preemption status are validated by the hybrid automaton
+constraints when processing the events::
+
+   |
+   |
+   v
+ #=========#   sched_need_resched;irq_off == 1
+ H         H   sched_waking;irq_off == 1 && preempt_off == 1
+ H   any   H ------------------------------------------------+
+ H         H                                                 |
+ H         H <-----------------------------------------------+
+ #=========#
 
 References
 ----------
diff --git a/kernel/trace/rv/monitors/opid/Kconfig b/kernel/trace/rv/monitors/opid/Kconfig
index 561d32da572b..6d02e239b684 100644
--- a/kernel/trace/rv/monitors/opid/Kconfig
+++ b/kernel/trace/rv/monitors/opid/Kconfig
@@ -2,18 +2,13 @@
 #
 config RV_MON_OPID
 	depends on RV
-	depends on TRACE_IRQFLAGS
-	depends on TRACE_PREEMPT_TOGGLE
 	depends on RV_MON_SCHED
-	default y if PREEMPT_RT
-	select DA_MON_EVENTS_IMPLICIT
+	default y
+	select HA_MON_EVENTS_IMPLICIT
 	bool "opid monitor"
 	help
 	  Monitor to ensure operations like wakeup and need resched occur with
-	  interrupts and preemption disabled or during IRQs, where preemption
-	  may not be disabled explicitly.
-
-	  This monitor is unstable on !PREEMPT_RT, say N unless you are testing it.
+	  interrupts and preemption disabled.
 
 	  For further information, see:
 	    Documentation/trace/rv/monitor_sched.rst
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 25a40e90fa40..160a518ce1cb 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -10,94 +10,63 @@
 #define MODULE_NAME "opid"
 
 #include <trace/events/sched.h>
-#include <trace/events/irq.h>
-#include <trace/events/preemptirq.h>
 #include <rv_trace.h>
 #include <monitors/sched/sched.h>
 
 #define RV_MON_TYPE RV_MON_PER_CPU
 #include "opid.h"
-#include <rv/da_monitor.h>
+#include <rv/ha_monitor.h>
 
-#ifdef CONFIG_X86_LOCAL_APIC
-#include <asm/trace/irq_vectors.h>
-
-static void handle_vector_irq_entry(void *data, int vector)
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_opid env, u64 time_ns)
 {
-	da_handle_event(irq_entry_opid);
-}
-
-static void attach_vector_irq(void)
-{
-	rv_attach_trace_probe("opid", local_timer_entry, handle_vector_irq_entry);
-	if (IS_ENABLED(CONFIG_IRQ_WORK))
-		rv_attach_trace_probe("opid", irq_work_entry, handle_vector_irq_entry);
-	if (IS_ENABLED(CONFIG_SMP)) {
-		rv_attach_trace_probe("opid", reschedule_entry, handle_vector_irq_entry);
-		rv_attach_trace_probe("opid", call_function_entry, handle_vector_irq_entry);
-		rv_attach_trace_probe("opid", call_function_single_entry, handle_vector_irq_entry);
+	if (env == irq_off_opid)
+		return irqs_disabled();
+	else if (env == preempt_off_opid) {
+		/*
+		 * If CONFIG_PREEMPTION is enabled, then the tracepoint itself disables
+		 * preemption (adding one to the preempt_count). Since we are
+		 * interested in the preempt_count at the time the tracepoint was
+		 * hit, we consider 1 as still enabled.
+		 */
+		if (IS_ENABLED(CONFIG_PREEMPTION))
+			return (preempt_count() & PREEMPT_MASK) > 1;
+		return true;
 	}
+	return ENV_INVALID_VALUE;
 }
 
-static void detach_vector_irq(void)
+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)
 {
-	rv_detach_trace_probe("opid", local_timer_entry, handle_vector_irq_entry);
-	if (IS_ENABLED(CONFIG_IRQ_WORK))
-		rv_detach_trace_probe("opid", irq_work_entry, handle_vector_irq_entry);
-	if (IS_ENABLED(CONFIG_SMP)) {
-		rv_detach_trace_probe("opid", reschedule_entry, handle_vector_irq_entry);
-		rv_detach_trace_probe("opid", call_function_entry, handle_vector_irq_entry);
-		rv_detach_trace_probe("opid", call_function_single_entry, handle_vector_irq_entry);
-	}
+	bool res = true;
+
+	if (curr_state == any_opid && event == sched_need_resched_opid)
+		res = ha_get_env(ha_mon, irq_off_opid, time_ns) == 1ull;
+	else if (curr_state == any_opid && event == sched_waking_opid)
+		res = ha_get_env(ha_mon, irq_off_opid, time_ns) == 1ull &&
+		      ha_get_env(ha_mon, preempt_off_opid, time_ns) == 1ull;
+	return res;
 }
 
-#else
-/* We assume irq_entry tracepoints are sufficient on other architectures */
-static void attach_vector_irq(void) { }
-static void detach_vector_irq(void) { }
-#endif
-
-static void handle_irq_disable(void *data, unsigned long ip, unsigned long parent_ip)
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+				 enum states curr_state, enum events event,
+				 enum states next_state, u64 time_ns)
 {
-	da_handle_event(irq_disable_opid);
-}
+	if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+		return false;
 
-static void handle_irq_enable(void *data, unsigned long ip, unsigned long parent_ip)
-{
-	da_handle_event(irq_enable_opid);
-}
-
-static void handle_irq_entry(void *data, int irq, struct irqaction *action)
-{
-	da_handle_event(irq_entry_opid);
-}
-
-static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
-{
-	da_handle_event(preempt_disable_opid);
-}
-
-static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
-{
-	da_handle_event(preempt_enable_opid);
+	return true;
 }
 
 static void handle_sched_need_resched(void *data, struct task_struct *tsk, int cpu, int tif)
 {
-	/* The monitor's intitial state is not in_irq */
-	if (this_cpu_read(hardirq_context))
-		da_handle_event(sched_need_resched_opid);
-	else
-		da_handle_start_event(sched_need_resched_opid);
+	da_handle_start_event(sched_need_resched_opid);
 }
 
 static void handle_sched_waking(void *data, struct task_struct *p)
 {
-	/* The monitor's intitial state is not in_irq */
-	if (this_cpu_read(hardirq_context))
-		da_handle_event(sched_waking_opid);
-	else
-		da_handle_start_event(sched_waking_opid);
+	da_handle_start_event(sched_waking_opid);
 }
 
 static int enable_opid(void)
@@ -108,14 +77,8 @@ static int enable_opid(void)
 	if (retval)
 		return retval;
 
-	rv_attach_trace_probe("opid", irq_disable, handle_irq_disable);
-	rv_attach_trace_probe("opid", irq_enable, handle_irq_enable);
-	rv_attach_trace_probe("opid", irq_handler_entry, handle_irq_entry);
-	rv_attach_trace_probe("opid", preempt_disable, handle_preempt_disable);
-	rv_attach_trace_probe("opid", preempt_enable, handle_preempt_enable);
 	rv_attach_trace_probe("opid", sched_set_need_resched_tp, handle_sched_need_resched);
 	rv_attach_trace_probe("opid", sched_waking, handle_sched_waking);
-	attach_vector_irq();
 
 	return 0;
 }
@@ -124,14 +87,8 @@ static void disable_opid(void)
 {
 	rv_this.enabled = 0;
 
-	rv_detach_trace_probe("opid", irq_disable, handle_irq_disable);
-	rv_detach_trace_probe("opid", irq_enable, handle_irq_enable);
-	rv_detach_trace_probe("opid", irq_handler_entry, handle_irq_entry);
-	rv_detach_trace_probe("opid", preempt_disable, handle_preempt_disable);
-	rv_detach_trace_probe("opid", preempt_enable, handle_preempt_enable);
 	rv_detach_trace_probe("opid", sched_set_need_resched_tp, handle_sched_need_resched);
 	rv_detach_trace_probe("opid", sched_waking, handle_sched_waking);
-	detach_vector_irq();
 
 	da_monitor_destroy();
 }
diff --git a/kernel/trace/rv/monitors/opid/opid.h b/kernel/trace/rv/monitors/opid/opid.h
index 092992514970..fb0aa4c28aa6 100644
--- a/kernel/trace/rv/monitors/opid/opid.h
+++ b/kernel/trace/rv/monitors/opid/opid.h
@@ -8,30 +8,31 @@
 #define MONITOR_NAME opid
 
 enum states_opid {
-	disabled_opid,
-	enabled_opid,
-	in_irq_opid,
-	irq_disabled_opid,
-	preempt_disabled_opid,
+	any_opid,
 	state_max_opid,
 };
 
 #define INVALID_STATE state_max_opid
 
 enum events_opid {
-	irq_disable_opid,
-	irq_enable_opid,
-	irq_entry_opid,
-	preempt_disable_opid,
-	preempt_enable_opid,
 	sched_need_resched_opid,
 	sched_waking_opid,
 	event_max_opid,
 };
 
+enum envs_opid {
+	irq_off_opid,
+	preempt_off_opid,
+	env_max_opid,
+	env_max_stored_opid = irq_off_opid,
+};
+
+_Static_assert(env_max_stored_opid <= MAX_HA_ENV_LEN, "Not enough slots");
+
 struct automaton_opid {
 	char *state_names[state_max_opid];
 	char *event_names[event_max_opid];
+	char *env_names[env_max_opid];
 	unsigned char function[state_max_opid][event_max_opid];
 	unsigned char initial_state;
 	bool final_states[state_max_opid];
@@ -39,68 +40,19 @@ struct automaton_opid {
 
 static const struct automaton_opid automaton_opid = {
 	.state_names = {
-		"disabled",
-		"enabled",
-		"in_irq",
-		"irq_disabled",
-		"preempt_disabled",
+		"any",
 	},
 	.event_names = {
-		"irq_disable",
-		"irq_enable",
-		"irq_entry",
-		"preempt_disable",
-		"preempt_enable",
 		"sched_need_resched",
 		"sched_waking",
 	},
+	.env_names = {
+		"irq_off",
+		"preempt_off",
+	},
 	.function = {
-		{
-			INVALID_STATE,
-			preempt_disabled_opid,
-			disabled_opid,
-			INVALID_STATE,
-			irq_disabled_opid,
-			disabled_opid,
-			disabled_opid,
-		},
-		{
-			irq_disabled_opid,
-			INVALID_STATE,
-			INVALID_STATE,
-			preempt_disabled_opid,
-			enabled_opid,
-			INVALID_STATE,
-			INVALID_STATE,
-		},
-		{
-			INVALID_STATE,
-			enabled_opid,
-			in_irq_opid,
-			INVALID_STATE,
-			INVALID_STATE,
-			in_irq_opid,
-			in_irq_opid,
-		},
-		{
-			INVALID_STATE,
-			enabled_opid,
-			in_irq_opid,
-			disabled_opid,
-			INVALID_STATE,
-			irq_disabled_opid,
-			INVALID_STATE,
-		},
-		{
-			disabled_opid,
-			INVALID_STATE,
-			INVALID_STATE,
-			INVALID_STATE,
-			enabled_opid,
-			INVALID_STATE,
-			INVALID_STATE,
-		},
+		{           any_opid,           any_opid },
 	},
-	.initial_state = disabled_opid,
-	.final_states = { 0, 1, 0, 0, 0 },
+	.initial_state = any_opid,
+	.final_states = { 1 },
 };
diff --git a/kernel/trace/rv/monitors/opid/opid_trace.h b/kernel/trace/rv/monitors/opid/opid_trace.h
index 3df6ff955c30..b04005b64208 100644
--- a/kernel/trace/rv/monitors/opid/opid_trace.h
+++ b/kernel/trace/rv/monitors/opid/opid_trace.h
@@ -12,4 +12,8 @@ DEFINE_EVENT(event_da_monitor, event_opid,
 DEFINE_EVENT(error_da_monitor, error_opid,
 	     TP_PROTO(char *state, char *event),
 	     TP_ARGS(state, event));
+
+DEFINE_EVENT(error_env_da_monitor, error_env_opid,
+	     TP_PROTO(char *state, char *event, char *env),
+	     TP_ARGS(state, event, env));
 #endif /* CONFIG_RV_MON_OPID */
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 1661f8fe4a88..8ac4f315a627 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -62,7 +62,6 @@ DECLARE_EVENT_CLASS(error_da_monitor,
 #include <monitors/scpd/scpd_trace.h>
 #include <monitors/snep/snep_trace.h>
 #include <monitors/sts/sts_trace.h>
-#include <monitors/opid/opid_trace.h>
 // Add new monitors based on CONFIG_DA_MON_EVENTS_IMPLICIT here
 
 #ifdef CONFIG_HA_MON_EVENTS_IMPLICIT
@@ -92,6 +91,7 @@ DECLARE_EVENT_CLASS(error_env_da_monitor,
 );
 
 // Add new monitors based on CONFIG_HA_MON_EVENTS_IMPLICIT here
+#include <monitors/opid/opid_trace.h>
 
 #endif
 
diff --git a/tools/verification/models/sched/opid.dot b/tools/verification/models/sched/opid.dot
index 840052f6952b..511051fce430 100644
--- a/tools/verification/models/sched/opid.dot
+++ b/tools/verification/models/sched/opid.dot
@@ -1,35 +1,13 @@
 digraph state_automaton {
 	center = true;
 	size = "7,11";
-	{node [shape = plaintext, style=invis, label=""] "__init_disabled"};
-	{node [shape = circle] "disabled"};
-	{node [shape = doublecircle] "enabled"};
-	{node [shape = circle] "enabled"};
-	{node [shape = circle] "in_irq"};
-	{node [shape = circle] "irq_disabled"};
-	{node [shape = circle] "preempt_disabled"};
-	"__init_disabled" -> "disabled";
-	"disabled" [label = "disabled"];
-	"disabled" -> "disabled" [ label = "sched_need_resched\nsched_waking\nirq_entry" ];
-	"disabled" -> "irq_disabled" [ label = "preempt_enable" ];
-	"disabled" -> "preempt_disabled" [ label = "irq_enable" ];
-	"enabled" [label = "enabled", color = green3];
-	"enabled" -> "enabled" [ label = "preempt_enable" ];
-	"enabled" -> "irq_disabled" [ label = "irq_disable" ];
-	"enabled" -> "preempt_disabled" [ label = "preempt_disable" ];
-	"in_irq" [label = "in_irq"];
-	"in_irq" -> "enabled" [ label = "irq_enable" ];
-	"in_irq" -> "in_irq" [ label = "sched_need_resched\nsched_waking\nirq_entry" ];
-	"irq_disabled" [label = "irq_disabled"];
-	"irq_disabled" -> "disabled" [ label = "preempt_disable" ];
-	"irq_disabled" -> "enabled" [ label = "irq_enable" ];
-	"irq_disabled" -> "in_irq" [ label = "irq_entry" ];
-	"irq_disabled" -> "irq_disabled" [ label = "sched_need_resched" ];
-	"preempt_disabled" [label = "preempt_disabled"];
-	"preempt_disabled" -> "disabled" [ label = "irq_disable" ];
-	"preempt_disabled" -> "enabled" [ label = "preempt_enable" ];
+	{node [shape = plaintext, style=invis, label=""] "__init_any"};
+	{node [shape = doublecircle] "any"};
+	"__init_any" -> "any";
+	"any" [label = "any", color = green3];
+	"any" -> "any" [ label = "sched_need_resched;irq_off == 1\nsched_waking;irq_off == 1 && preempt_off == 1" ];
 	{ rank = min ;
-		"__init_disabled";
-		"disabled";
+		"__init_any";
+		"any";
 	}
 }
-- 
2.51.0


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

* [PATCH v2 16/20] sched: Export hidden tracepoints to modules
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (14 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-09-19 15:37   ` Steven Rostedt
  2025-09-19 14:09 ` [PATCH v2 17/20] sched: Add deadline tracepoints Gabriele Monaco
                   ` (4 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Ingo Molnar, Peter Zijlstra
  Cc: Gabriele Monaco, Steven Rostedt, Nam Cao, Tomas Glozar,
	Juri Lelli, Clark Williams, John Kacur, linux-trace-kernel

The tracepoints sched_entry, sched_exit and sched_set_need_resched
are not exported to tracefs as trace events, this allows only kernel
code to access them. Helper modules like [1] can be used to still have
the tracepoints available to ftrace for debugging purposes, but they do
rely on the tracepoints being exported.

Export the 3 not exported tracepoints.
Note that sched_set_state is already exported as the macro is called
from modules.

[1] - https://github.com/qais-yousef/sched_tp.git

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/sched/core.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index ccba6fc3c3fe..334ff5b214d7 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -117,6 +117,9 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_cfs_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_se_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_update_nr_running_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
 
 DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
 
-- 
2.51.0


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

* [PATCH v2 17/20] sched: Add deadline tracepoints
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (15 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 16/20] sched: Export hidden tracepoints to modules Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-09-19 14:09 ` [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
                   ` (3 subsequent siblings)
  20 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Masami Hiramatsu, Ingo Molnar,
	Peter Zijlstra, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Add the following tracepoints:

* sched_dl_throttle(dl):
    Called when a deadline entity is throttled
* sched_dl_replenish(dl):
    Called when a deadline entity's runtime is replenished
* sched_dl_server_start(dl):
    Called when a deadline server is started
* sched_dl_server_stop(dl, hard):
    Called when a deadline server is stopped (hard) or put to idle
    waiting for the next period (!hard)

Those tracepoints can be useful to validate the deadline scheduler with
RV and are not exported to tracefs.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/trace/events/sched.h | 16 ++++++++++++++++
 kernel/sched/core.c          |  4 ++++
 kernel/sched/deadline.c      |  9 +++++++++
 3 files changed, 29 insertions(+)

diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
index 7b2645b50e78..6dc5cd3e9fc4 100644
--- a/include/trace/events/sched.h
+++ b/include/trace/events/sched.h
@@ -896,6 +896,22 @@ DECLARE_TRACE(sched_set_need_resched,
 	TP_PROTO(struct task_struct *tsk, int cpu, int tif),
 	TP_ARGS(tsk, cpu, tif));
 
+DECLARE_TRACE(sched_dl_throttle,
+	TP_PROTO(struct sched_dl_entity *dl, int cpu),
+	TP_ARGS(dl, cpu));
+
+DECLARE_TRACE(sched_dl_replenish,
+	TP_PROTO(struct sched_dl_entity *dl, int cpu),
+	TP_ARGS(dl, cpu));
+
+DECLARE_TRACE(sched_dl_server_start,
+	TP_PROTO(struct sched_dl_entity *dl, int cpu),
+	TP_ARGS(dl, cpu));
+
+DECLARE_TRACE(sched_dl_server_stop,
+	TP_PROTO(struct sched_dl_entity *dl, int cpu, bool hard),
+	TP_ARGS(dl, cpu, hard));
+
 #endif /* _TRACE_SCHED_H */
 
 /* This part must be outside protection */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 334ff5b214d7..b236fc762e73 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -120,6 +120,10 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_throttle_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_replenish_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_server_start_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_server_stop_tp);
 
 DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
 
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index f25301267e47..187175607682 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -742,6 +742,7 @@ static inline void replenish_dl_new_period(struct sched_dl_entity *dl_se,
 		dl_se->dl_throttled = 1;
 		dl_se->dl_defer_armed = 1;
 	}
+	trace_sched_dl_replenish_tp(dl_se, cpu_of(rq));
 }
 
 /*
@@ -852,6 +853,9 @@ static void replenish_dl_entity(struct sched_dl_entity *dl_se)
 	if (dl_time_before(dl_se->deadline, rq_clock(rq))) {
 		printk_deferred_once("sched: DL replenish lagged too much\n");
 		replenish_dl_new_period(dl_se, rq);
+	} else {
+		/* replenish_dl_new_period is also tracing */
+		trace_sched_dl_replenish_tp(dl_se, cpu_of(rq));
 	}
 
 	if (dl_se->dl_yielded)
@@ -1349,6 +1353,7 @@ static inline void dl_check_constrained_dl(struct sched_dl_entity *dl_se)
 	    dl_time_before(rq_clock(rq), dl_next_period(dl_se))) {
 		if (unlikely(is_dl_boosted(dl_se) || !start_dl_timer(dl_se)))
 			return;
+		trace_sched_dl_throttle_tp(dl_se, cpu_of(rq));
 		dl_se->dl_throttled = 1;
 		if (dl_se->runtime > 0)
 			dl_se->runtime = 0;
@@ -1482,6 +1487,7 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
 
 throttle:
 	if (dl_runtime_exceeded(dl_se) || dl_se->dl_yielded) {
+		trace_sched_dl_throttle_tp(dl_se, cpu_of(rq));
 		dl_se->dl_throttled = 1;
 
 		/* If requested, inform the user about runtime overruns. */
@@ -1592,6 +1598,7 @@ void dl_server_start(struct sched_dl_entity *dl_se)
 	if (!dl_server(dl_se) || dl_se->dl_server_active)
 		return;
 
+	trace_sched_dl_server_start_tp(dl_se, cpu_of(rq));
 	dl_se->dl_server_active = 1;
 	enqueue_dl_entity(dl_se, ENQUEUE_WAKEUP);
 	if (!dl_task(dl_se->rq->curr) || dl_entity_preempt(dl_se, &rq->curr->dl))
@@ -1603,6 +1610,7 @@ void dl_server_stop(struct sched_dl_entity *dl_se)
 	if (!dl_server(dl_se) || !dl_server_active(dl_se))
 		return;
 
+	trace_sched_dl_server_stop_tp(dl_se, cpu_of(dl_se->rq), true);
 	dequeue_dl_entity(dl_se, DEQUEUE_SLEEP);
 	hrtimer_try_to_cancel(&dl_se->dl_timer);
 	dl_se->dl_defer_armed = 0;
@@ -1620,6 +1628,7 @@ static bool dl_server_stopped(struct sched_dl_entity *dl_se)
 		return true;
 	}
 
+	trace_sched_dl_server_stop_tp(dl_se, cpu_of(dl_se->rq), false);
 	dl_se->dl_server_idle = 1;
 	return false;
 }
-- 
2.51.0


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

* [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (16 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 17/20] sched: Add deadline tracepoints Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-21 11:55   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
                   ` (2 subsequent siblings)
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

RV deterministic and hybrid automata currently only support global,
per-cpu and per-task monitors. It isn't possible to write a model that
would follow some different type of object, like a deadline entity or a
lock.

Define the generic per-object monitor implementation which shares part
of the implementation with the per-task monitors.
The user needs to provide an id for the object (e.g. pid for tasks) and
define the data type for the monitor_target (e.g. struct task_struct *
for tasks). Both are supplied to the event handlers, as the id may not
be easily available in the target.

The monitor storage (e.g. the rv monitor, pointer to the target, etc.)
is stored in a hash table indexed by id. Monitor storage objects are
automatically allocated unless specified otherwise (e.g. if the creation
context is unsafe for allocation).

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/linux/rv.h      |   1 +
 include/rv/da_monitor.h | 323 +++++++++++++++++++++++++++++++++++++++-
 include/rv/ha_monitor.h |   5 +-
 3 files changed, 323 insertions(+), 6 deletions(-)

diff --git a/include/linux/rv.h b/include/linux/rv.h
index 90d51e00e99c..c30a78026764 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -16,6 +16,7 @@
 #define RV_MON_GLOBAL   0
 #define RV_MON_PER_CPU  1
 #define RV_MON_PER_TASK 2
+#define RV_MON_PER_OBJ  3
 
 #ifdef CONFIG_RV
 #include <linux/bitops.h>
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index afbe0807ed11..ead24394d146 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -19,6 +19,8 @@
 #include <linux/stringify.h>
 #include <linux/bug.h>
 #include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/hashtable.h>
 
 #define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
 static struct rv_monitor rv_this;
@@ -52,11 +54,34 @@ static struct rv_monitor rv_this;
 
 /*
  * Type for the target id, default to int but can be overridden.
+ * A long type can work as hash table key (PER_OBJ) but will be downgraded to
+ * int in the event tracepoint.
+ * Unused for implicit monitors.
  */
 #ifndef da_id_type
 #define da_id_type int
 #endif
 
+/*
+ * Allow the per-object monitors to run allocation manually, necessary if the
+ * start condition is in a context problematic for allocation (e.g. scheduling).
+ * In such case, if the storage was pre-allocated without a target, set it now.
+ */
+#if RV_MON_TYPE == RV_MON_PER_OBJ
+#ifdef DA_SKIP_AUTO_ALLOC
+static inline struct da_monitor *da_fill_empty_storage(da_id_type id,
+						       monitor_target target,
+						       struct da_monitor *da_mon);
+#define da_monitor_start_hook da_fill_empty_storage
+#else
+#define da_monitor_start_hook(...) da_create_storage(__VA_ARGS__, GFP_NOWAIT)
+static inline struct da_monitor *da_create_storage(da_id_type id,
+						   monitor_target target,
+						   struct da_monitor *da_mon,
+						   gfp_t flags);
+#endif /* DA_SKIP_AUTO_ALLOC */
+#endif
+
 #ifdef CONFIG_RV_REACTORS
 
 static void cond_react(enum states curr_state, enum events event)
@@ -241,9 +266,9 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
 }
 
 /*
- * da_get_task - return the task associated to the monitor
+ * da_get_target - return the task associated to the monitor
  */
-static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
+static inline struct task_struct *da_get_target(struct da_monitor *da_mon)
 {
 	return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon);
 }
@@ -255,7 +280,7 @@ static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
  */
 static inline da_id_type da_get_id(struct da_monitor *da_mon)
 {
-	return da_get_task(da_mon)->pid;
+	return da_get_target(da_mon)->pid;
 }
 
 static void da_monitor_reset_all(void)
@@ -303,6 +328,220 @@ static inline void da_monitor_destroy(void)
 	rv_put_task_monitor_slot(task_mon_slot);
 	task_mon_slot = RV_PER_TASK_MONITOR_INIT;
 }
+
+#elif RV_MON_TYPE == RV_MON_PER_OBJ
+/*
+ * Functions to define, init and get a per-object monitor.
+ */
+
+static struct kmem_cache *da_monitor_cache;
+
+struct da_monitor_storage {
+	da_id_type id;
+	monitor_target target;
+	union rv_task_monitor rv;
+	struct hlist_node node;
+	struct rcu_head rcu;
+};
+
+#ifndef DA_MONITOR_HT_BITS
+#define DA_MONITOR_HT_BITS 10
+#endif
+static DEFINE_HASHTABLE(da_monitor_ht, DA_MONITOR_HT_BITS);
+
+/*
+ * da_create_empty_storage - pre-allocate an empty storage
+ */
+static inline struct da_monitor_storage *da_create_empty_storage(da_id_type id,
+								 gfp_t flags)
+{
+	struct da_monitor_storage *mon_storage;
+
+	mon_storage = kmem_cache_zalloc(da_monitor_cache, flags);
+	if (!mon_storage)
+		return NULL;
+
+	hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
+	mon_storage->id = id;
+	return mon_storage;
+}
+
+/*
+ * da_create_storage - create the per-object storage
+ *
+ * The caller is responsible to synchronise writers, either with locks or
+ * implicitly. For instance, if da_create_storage is only called from a single
+ * event for target (e.g. sched_switch), it's safe to call this without locks.
+ */
+static inline struct da_monitor *da_create_storage(da_id_type id,
+						   monitor_target target,
+						   struct da_monitor *da_mon,
+						   gfp_t flags)
+{
+	struct da_monitor_storage *mon_storage;
+
+	if (da_mon)
+		return da_mon;
+
+	mon_storage = da_create_empty_storage(id, flags);
+	if (!mon_storage)
+		return NULL;
+
+	mon_storage->target = target;
+	return &mon_storage->rv.da_mon;
+}
+
+/*
+ * __da_get_mon_storage - get the monitor storage from the hash table
+ */
+static inline struct da_monitor_storage *__da_get_mon_storage(da_id_type id)
+{
+	struct da_monitor_storage *mon_storage;
+
+	lockdep_assert_in_rcu_read_lock();
+	hash_for_each_possible_rcu(da_monitor_ht, mon_storage, node, id) {
+		if (mon_storage->id == id)
+			return mon_storage;
+	}
+
+	return NULL;
+}
+
+/*
+ * da_get_monitor - return the monitor for target
+ */
+static struct da_monitor *da_get_monitor(da_id_type id, monitor_target target)
+{
+	struct da_monitor_storage *mon_storage;
+
+	mon_storage = __da_get_mon_storage(id);
+	return mon_storage ? &mon_storage->rv.da_mon : NULL;
+}
+
+/*
+ * da_get_target - return the object associated to the monitor
+ */
+static inline monitor_target da_get_target(struct da_monitor *da_mon)
+{
+	return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target;
+}
+
+/*
+ * da_get_id - return the id associated to the monitor
+ */
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+	return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->id;
+}
+
+/*
+ * da_create_conditional - create the per-object storage if not already there
+ *
+ * This needs a lookup so should be guarded by RCU, the condition is checked
+ * directly in da_create_storage()
+ */
+static inline void da_create_conditional(da_id_type id, monitor_target target,
+					 gfp_t flags)
+{
+	guard(rcu)();
+	da_create_storage(id, target, da_get_monitor(id, target), flags);
+}
+
+/*
+ * da_fill_empty_storage - store the target in a pre-allocated storage
+ *
+ * Can be used as a substitute of da_create_storage when starting a monitor in
+ * an environment where allocation is unsafe.
+ */
+static inline struct da_monitor *da_fill_empty_storage(da_id_type id,
+						       monitor_target target,
+						       struct da_monitor *da_mon)
+{
+	if (unlikely(da_mon && !da_get_target(da_mon)))
+		container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target = target;
+	return da_mon;
+}
+
+/*
+ * da_get_target_by_id - return the object associated to the id
+ */
+static inline monitor_target da_get_target_by_id(da_id_type id)
+{
+	struct da_monitor_storage *mon_storage;
+
+	guard(rcu)();
+	mon_storage = __da_get_mon_storage(id);
+
+	if (unlikely(!mon_storage))
+		return NULL;
+	return mon_storage->target;
+}
+
+/*
+ * da_destroy_storage - destroy the per-object storage
+ *
+ * The caller is responsible to synchronise writers, either with locks or
+ * implicitly. For instance, if da_destroy_storage is called at sched_exit and
+ * da_create_storage can never occur after that, it's safe to call this without
+ * locks.
+ * This function includes an RCU read-side critical section to synchronise
+ * against da_monitor_destroy().
+ */
+static inline void da_destroy_storage(da_id_type id)
+{
+	struct da_monitor_storage *mon_storage;
+
+	guard(rcu)();
+	mon_storage = __da_get_mon_storage(id);
+
+	if (!mon_storage)
+		return;
+	da_monitor_reset(&mon_storage->rv.da_mon);
+	hash_del_rcu(&mon_storage->node);
+	kfree_rcu(mon_storage, rcu);
+}
+
+static void da_monitor_reset_all(void)
+{
+	struct da_monitor_storage *mon_storage;
+	int bkt;
+
+	rcu_read_lock();
+	hash_for_each_rcu(da_monitor_ht, bkt, mon_storage, node)
+		da_monitor_reset(&mon_storage->rv.da_mon);
+	rcu_read_unlock();
+}
+
+static inline int da_monitor_init(void)
+{
+	hash_init(da_monitor_ht);
+	da_monitor_cache = kmem_cache_create(__stringify(MONITOR_NAME) "-cache",
+					     sizeof(struct da_monitor_storage),
+					     NULL, 0);
+	if (!da_monitor_cache)
+		return -ENOMEM;
+	return 0;
+}
+
+static inline void da_monitor_destroy(void)
+{
+	struct da_monitor_storage *mon_storage;
+	struct hlist_node *tmp;
+	int bkt;
+
+	/*
+	 * This function is called after all probes are disabled, we need only
+	 * worry about concurrency against old events.
+	 */
+	synchronize_rcu();
+	hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
+		hash_del_rcu(&mon_storage->node);
+		kfree(mon_storage);
+	}
+	rcu_barrier();
+	kmem_cache_destroy(da_monitor_cache);
+}
+
 #endif /* RV_MON_TYPE */
 
 #if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
@@ -336,9 +575,9 @@ static inline da_id_type da_get_id(struct da_monitor *da_mon)
 	return 0;
 }
 
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
 /*
- * Trace events for per_task monitors, report the PID of the task.
+ * Trace events for per_task/per_object monitors, report the target id.
  */
 
 static inline void da_trace_event(struct da_monitor *da_mon,
@@ -531,6 +770,80 @@ static inline bool da_handle_start_run_event(struct task_struct *tsk,
 		return 0;
 	return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid);
 }
+
+#elif RV_MON_TYPE == RV_MON_PER_OBJ
+/*
+ * Handle event for per object.
+ */
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event(da_id_type id, monitor_target target, enum events event)
+{
+	struct da_monitor *da_mon;
+
+	guard(rcu)();
+	da_mon = da_get_monitor(id, target);
+	if (likely(da_mon))
+		__da_handle_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_event - start monitoring or handle event
+ *
+ * This function is used to notify the monitor that the system is returning
+ * to the initial state, so the monitor can start monitoring in the next event.
+ * Thus:
+ *
+ * If the monitor already started, handle the event.
+ * If the monitor did not start yet, start the monitor but skip the event.
+ */
+static inline bool da_handle_start_event(da_id_type id, monitor_target target,
+					 enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+	guard(rcu)();
+	da_mon = da_get_monitor(id, target);
+	da_mon = da_monitor_start_hook(id, target, da_mon);
+	if (unlikely(!da_mon))
+		return 0;
+	return __da_handle_start_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_run_event - start monitoring and handle event
+ *
+ * This function is used to notify the monitor that the system is in the
+ * initial state, so the monitor can start monitoring and handling event.
+ */
+static inline bool da_handle_start_run_event(da_id_type id, monitor_target target,
+					     enum events event)
+{
+	struct da_monitor *da_mon;
+
+	if (!da_monitor_enabled())
+		return 0;
+	guard(rcu)();
+	da_mon = da_get_monitor(id, target);
+	da_mon = da_monitor_start_hook(id, target, da_mon);
+	if (unlikely(!da_mon))
+		return 0;
+	return __da_handle_start_run_event(da_mon, event, id);
+}
+
+static inline void da_reset(da_id_type id, monitor_target target)
+{
+	struct da_monitor *da_mon;
+
+	guard(rcu)();
+	da_mon = da_get_monitor(id, target);
+	if (likely(da_mon))
+		da_monitor_reset(da_mon);
+}
 #endif /* RV_MON_TYPE */
 
 #endif
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index fb885709b727..dcc2a4d4af65 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -190,7 +190,10 @@ static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
 {
 	CONCATENATE(trace_error_env_, MONITOR_NAME)(curr_state, event, env);
 }
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+
+#define ha_get_target(ha_mon) da_get_target(&ha_mon->da_mon)
+
 static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
 				      char *curr_state, char *event, char *env,
 				      da_id_type id)
-- 
2.51.0


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

* [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (17 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-21 12:00   ` Nam Cao
  2025-09-19 14:09 ` [PATCH v2 20/20] rv: Add deadline monitors Gabriele Monaco
  2025-10-21 12:05 ` [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and " Nam Cao
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

The special per-object monitor type was just introduced in RV, this
requires the user to define some functions and type specific to the
object.

Adapt rvgen to add stub definitions for the monitor_target type, the
da_get_id() function and other modifications required to create
per-object monitors.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/rvgen/dot2k.py     | 6 ++++++
 tools/verification/rvgen/rvgen/generator.py | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index 627efaec3a59..fe05bdc09861 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -27,6 +27,8 @@ class dot2k(Monitor, Dot2c):
     def fill_monitor_type(self) -> str:
         buff = [ self.monitor_type.upper() ]
         buff += self._fill_timer_type()
+        if self.monitor_type == "per_obj":
+            buff.append("typedef /* XXX: define the target type */ *monitor_target;")
         return "\n".join(buff)
 
     def fill_tracepoint_handlers_skel(self) -> str:
@@ -45,6 +47,10 @@ class dot2k(Monitor, Dot2c):
             if self.monitor_type == "per_task":
                 buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;");
                 buff.append("\tda_%s(p, %s%s);" % (handle, event, self.enum_suffix));
+            elif self.monitor_type == "per_obj":
+                buff.append("\tint id = /* XXX: how do I get the id? */;");
+                buff.append("\tmonitor_target t = /* XXX: how do I get t? */;");
+                buff.append("\tda_%s(id, t, %s%s);" % (handle, event, self.enum_suffix));
             else:
                 buff.append("\tda_%s(%s%s);" % (handle, event, self.enum_suffix));
             buff.append("}")
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index b80af3fd6701..5eac12e110dc 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -243,7 +243,7 @@ obj-$(CONFIG_RV_MON_%s) += monitors/%s/%s.o
 
 
 class Monitor(RVGenerator):
-    monitor_types = { "global" : 1, "per_cpu" : 2, "per_task" : 3 }
+    monitor_types = { "global" : 1, "per_cpu" : 2, "per_task" : 3, "per_obj" : 4 }
 
     def __init__(self, extra_params={}):
         super().__init__(extra_params)
-- 
2.51.0


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

* [PATCH v2 20/20] rv: Add deadline monitors
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (18 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
@ 2025-09-19 14:09 ` Gabriele Monaco
  2025-10-10 15:04   ` Nam Cao
  2025-10-21 12:05 ` [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and " Nam Cao
  20 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 14:09 UTC (permalink / raw)
  To: linux-kernel, Steven Rostedt, Jonathan Corbet, Masami Hiramatsu,
	linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
	Clark Williams, John Kacur

Add the deadline monitors collection to validate the deadline scheduler,
both for deadline tasks and servers.

The currently implemented monitors are:
* throttle:
    validate dl entities are throttled when they use up their runtime
* nomiss:
    validate dl entities run to completion before their deadiline

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 Documentation/trace/rv/index.rst              |   1 +
 Documentation/trace/rv/monitor_deadline.rst   | 151 ++++++++++
 kernel/trace/rv/Kconfig                       |   5 +
 kernel/trace/rv/Makefile                      |   3 +
 kernel/trace/rv/monitors/deadline/Kconfig     |  10 +
 kernel/trace/rv/monitors/deadline/deadline.c  |  35 +++
 kernel/trace/rv/monitors/deadline/deadline.h  | 200 +++++++++++++
 kernel/trace/rv/monitors/nomiss/Kconfig       |  15 +
 kernel/trace/rv/monitors/nomiss/nomiss.c      | 283 ++++++++++++++++++
 kernel/trace/rv/monitors/nomiss/nomiss.h      | 130 ++++++++
 .../trace/rv/monitors/nomiss/nomiss_trace.h   |  19 ++
 kernel/trace/rv/monitors/throttle/Kconfig     |  15 +
 kernel/trace/rv/monitors/throttle/throttle.c  | 253 ++++++++++++++++
 kernel/trace/rv/monitors/throttle/throttle.h  | 118 ++++++++
 .../rv/monitors/throttle/throttle_trace.h     |  19 ++
 kernel/trace/rv/rv_trace.h                    |   2 +
 tools/verification/models/deadline/nomiss.dot |  39 +++
 .../verification/models/deadline/throttle.dot |  44 +++
 18 files changed, 1342 insertions(+)
 create mode 100644 Documentation/trace/rv/monitor_deadline.rst
 create mode 100644 kernel/trace/rv/monitors/deadline/Kconfig
 create mode 100644 kernel/trace/rv/monitors/deadline/deadline.c
 create mode 100644 kernel/trace/rv/monitors/deadline/deadline.h
 create mode 100644 kernel/trace/rv/monitors/nomiss/Kconfig
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.c
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.h
 create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_trace.h
 create mode 100644 kernel/trace/rv/monitors/throttle/Kconfig
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle.c
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle.h
 create mode 100644 kernel/trace/rv/monitors/throttle/throttle_trace.h
 create mode 100644 tools/verification/models/deadline/nomiss.dot
 create mode 100644 tools/verification/models/deadline/throttle.dot

diff --git a/Documentation/trace/rv/index.rst b/Documentation/trace/rv/index.rst
index bf9962f49959..29769f06bb0f 100644
--- a/Documentation/trace/rv/index.rst
+++ b/Documentation/trace/rv/index.rst
@@ -17,3 +17,4 @@ Runtime Verification
    monitor_sched.rst
    monitor_rtapp.rst
    monitor_stall.rst
+   monitor_deadline.rst
diff --git a/Documentation/trace/rv/monitor_deadline.rst b/Documentation/trace/rv/monitor_deadline.rst
new file mode 100644
index 000000000000..941b93747204
--- /dev/null
+++ b/Documentation/trace/rv/monitor_deadline.rst
@@ -0,0 +1,151 @@
+Scheduler monitors
+==================
+
+- Name: deadline
+- Type: container for multiple monitors
+- Author: Gabriele Monaco <gmonaco@redhat.com>
+
+Description
+-----------
+
+The deadline monitor is a set of specifications to describe the deadline
+scheduler behaviour. It includes monitors per scheduling entity (deadline tasks
+and servers) that work independently to verify different specifications the
+deadline scheduler should follow.
+
+Specifications
+--------------
+
+Monitor throttle
+~~~~~~~~~~~~~~~~
+
+The throttle monitor ensures deadline entities are throttled when they use up
+their runtime. Deadline tasks can be only ``running``, ``preempted`` and
+``throttled``, the runtime is enforced only in ``running`` based on an internal
+clock and the runtime value in the deadline entity.
+
+Servers can be also in the ``armed`` state, which corresponds to when the
+server is consuming bandwidth in background (e.g. idle or normal tasks are
+running without any boost). From this state the server can be throttled but it
+can also use more runtime than available. A server is considered ``running``
+when it's actively boosting a task, only there the runtime is enforced. The
+server is preempted if the running task is not in the server's runqueue (e.g. a
+FIFO task for the fair server).
+Events like ``dl_armed`` and ``sched_switch_in`` can occur sequentially for
+servers since they are related to the current task (e.g. a 2 fair tasks can be
+switched in sequentially, that corresponds to multiple ``dl_armed``).
+
+Any task or server in the ``throttled`` state must leave it shortly, e.g.
+become ``preempted``::
+
+                                     |
+                                     |
+      dl_replenish;reset(clk)        v
+              sched_switch_in   #=========================# sched_switch_in;
+               +--------------- H                         H   reset(clk)
+               |                H                         H <----------------+
+               +--------------> H         running         H                  |
+    dl_throttle;reset(clk)      H clk < runtime_left_ns() H                  |
+   +--------------------------- H                         H sched_switch_out |
+   |       +------------------> H                         H -------------+   |
+   | dl_replenish;reset(clk)    #=========================#              |   |
+   |       |                         |             ^                     |   |
+   v       |                  dl_defer_arm         |                     |   |
+ +-------------------------+         |             |                     |   |
+ |       throttled         |         |    sched_switch_in;reset(clk)     |   |
+ | clk < THROTTLED_TIME_NS |         v             |                     |   |
+ +-------------------------+        +----------------+                   |   |
+   |    |                           |                | sched_switch_out  |   |
+   |    |               +---------- |                | -------------+    |   |
+   |    |          dl_replenish     |     armed      |              |    |   |
+   |    |          dl_defer_arm     |                | <--------+   |    |   |
+   |    |               +---------> |                | dl_defer_arm |    |   |
+   |    |                           +----------------+          |   |    |   |
+   |    |                               |         ^             |   |    |   |
+   |    |                           dl_throttle  dl_replenish   |   |    |   |
+   |    |   dl_throttle;yielded==1      v         |             |   |    |   |
+   |    |        dl_defer_arm      +-------------------+        |   v    v   |
+   |    |              +---------- |                   |      +--------------+
+   |    |              |           |                   |      |              |
+   |    |              +---------> |  armed_throttled  |      |  preempted   |
+   |    |                          |                   |      |              |
+   |    +------------------------> |                   |      +--------------+
+   |        dl_defer_arm           +-------------------+                 |  ^
+   |                                 |              ^                    |  |
+   |                       sched_switch_out    dl_defer_arm              |  |
+   |                                 v              |                    |  |
+   |       sched_switch_out  +-----------------------+                   |  |
+   |         +-------------- |                       | dl_throttle;      |  |
+   |         |               |                       |  is_constr_dl==1  |  |
+   |         +-------------> |  preempted_throttled  | <-----------------+  |
+   |                         |                       |                      |
+   +-----------------------> |                       | -- dl_replenish -----+
+         sched_switch_out    +-----------------------+
+
+The value of ``runtime_left_ns()`` is directly read from the deadline entity
+and updated as the task runs. It is increased by 1 tick to account for the
+maximum delay to throttle (not valid if ``sched_feat(HRTICK_DL)`` is active).
+
+Monitor nomiss
+~~~~~~~~~~~~~~
+
+The nomiss monitor ensures dl entities get to run *and* run to completion
+before their deadiline, although deferrable servers may not run. An entity is
+considered done if ``throttled``, either because it yielded or used up its
+runtime, or when it voluntarily starts ``sleeping``.
+The monitor includes a user configurable deadline threshold. If the total
+utilisation of deadline tasks is larger than 1, they are only guaranteed
+bounded tardiness. See Documentation/scheduler/sched-deadline.rst for more
+details. The threshold (module parameter ``nomiss.deadline_thresh``) can be
+configured to avoid the monitor to fail based on the acceptable tardiness in
+the system. Since ``dl_throttle`` is a valid outcome for the entity to be done,
+the minimum tardiness needs be 1 tick to consider the throttle delay.
+
+Servers have also an intermediate ``idle`` state, occurring as soon as no
+runnable task is available. When a server goes to ``sleeping`` it is guaranteed
+to be done for the period (unlike tasks), hence it cannot be considered
+``ready`` before its runtime is replenished::
+
+              sched_wakeup        |
+           dl_server_start        |
+   dl_replenish;reset(clk)        v
+               +------------ #=========================#
+               |             H                         H dl_replenish;reset(clk)
+               +-----------> H                         H <------------------+
+                             H                         H                    |
+ +- dl_server_stop --------- H          ready          H                    |
+ |                           H   clk < DEADLINE_NS()   H                    |
+ |    +--------------------> H                         H   dl_throttle;     |
+ |    |                      H                         H   is_defer == 1    |
+ |    |   sched_switch_in -- H                         H ---------------+   |
+ |    |    |                 #=========================#                |   |
+ |    |    |                       |             ^                      |   |
+ |    |    |             dl_server_idle     dl_replenish;reset(clk)     |   |
+ |    |    |                       v             |                      |   |
+ |    |    |                      +----------------+                    |   |
+ |    |    |          +---------- |                |                    |   |
+ |    |    |       dl_server_idle |      idle      | dl_throttle        |   |
+ |    |    |          +---------> |                | ---------------+   |   |
+ |    |    |                      +----------------+                |   |   |
+ |    |    |                       |             ^                  |   |   |
+ |    |    |            sched_switch_in      dl_server_idle         |   |   |
+ |    |    |                       v             |                  |   |   |
+ |    |    |        +---------- +---------------------+             |   |   |
+ |    |    |   sched_switch_in  |                     |             |   |   |
+ |    |    |   sched_wakeup     |                     | dl_throttle |   |   |
+ |    |    |   dl_replenish;    |      running        | -------+    |   |   |
+ |    |    |        reset(clk)  | clk < DEADLINE_NS() |        |    |   |   |
+ |    |    |        +---------> |                     |        |    |   |   |
+ |    |    +------------------> |                     |        |    |   |   |
+ |    |                         +---------------------+        |    |   |   |
+ |  sched_wakeup                   | sched_switch_suspend      |    |   |   |
+ v  dl_replenish;reset(clk)        | dl_server_stop            |    |   |   |
+ +---------------+                 |                           v    v   v   |
+ |               | <---------------+          dl_throttle     +--------------+
+ |               |                           sched_wakeup +-- |              |
+ |   sleeping    | --+                     dl_server_idle |   |   throttled  |
+ |               |  dl_server_start       dl_server_start +-> |              |
+ |               |  dl_server_idle      sched_switch_suspend  +--------------+
+ +---------------+ <-+                                                ^
+         |                 dl_throttle;is_constr_dl == 1              |
+         +------------------------------------------------------------+
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 720fbe4935f8..719cdcfb6d41 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -79,6 +79,11 @@ source "kernel/trace/rv/monitors/sleep/Kconfig"
 # Add new rtapp monitors here
 
 source "kernel/trace/rv/monitors/stall/Kconfig"
+source "kernel/trace/rv/monitors/deadline/Kconfig"
+source "kernel/trace/rv/monitors/nomiss/Kconfig"
+source "kernel/trace/rv/monitors/throttle/Kconfig"
+# Add new deadline monitors here
+
 # Add new monitors here
 
 config RV_REACTORS
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 51c95e2d2da6..15a1edc8bd0f 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -18,6 +18,9 @@ obj-$(CONFIG_RV_MON_NRP) += monitors/nrp/nrp.o
 obj-$(CONFIG_RV_MON_SSSW) += monitors/sssw/sssw.o
 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_THROTTLE) += monitors/throttle/throttle.o
 # Add new monitors here
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
diff --git a/kernel/trace/rv/monitors/deadline/Kconfig b/kernel/trace/rv/monitors/deadline/Kconfig
new file mode 100644
index 000000000000..38804a6ad91d
--- /dev/null
+++ b/kernel/trace/rv/monitors/deadline/Kconfig
@@ -0,0 +1,10 @@
+config RV_MON_DEADLINE
+	depends on RV
+	bool "deadline monitor"
+	help
+	  Collection of monitors to check the deadline scheduler and server
+	  behave according to specifications. Enable this to enable all
+	  scheduler specification supported by the current kernel.
+
+	  For further information, see:
+	    Documentation/trace/rv/monitor_deadline.rst
diff --git a/kernel/trace/rv/monitors/deadline/deadline.c b/kernel/trace/rv/monitors/deadline/deadline.c
new file mode 100644
index 000000000000..45aed62c1371
--- /dev/null
+++ b/kernel/trace/rv/monitors/deadline/deadline.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 "deadline"
+
+#include "deadline.h"
+
+struct rv_monitor rv_deadline = {
+	.name = "deadline",
+	.description = "container for several deadline scheduler specifications.",
+	.enable = NULL,
+	.disable = NULL,
+	.reset = NULL,
+	.enabled = 0,
+};
+
+static int __init register_deadline(void)
+{
+	return rv_register_monitor(&rv_deadline, NULL);
+}
+
+static void __exit unregister_deadline(void)
+{
+	rv_unregister_monitor(&rv_deadline);
+}
+
+module_init(register_deadline);
+module_exit(unregister_deadline);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("deadline: container for several deadline scheduler specifications.");
diff --git a/kernel/trace/rv/monitors/deadline/deadline.h b/kernel/trace/rv/monitors/deadline/deadline.h
new file mode 100644
index 000000000000..76f2770a7246
--- /dev/null
+++ b/kernel/trace/rv/monitors/deadline/deadline.h
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/kernel.h>
+#include <asm/syscall.h>
+#include <uapi/linux/sched/types.h>
+
+/*
+ * Dummy values if not available
+ */
+#ifndef __NR_sched_setscheduler
+#define __NR_sched_setscheduler -1
+#endif
+#ifndef __NR_sched_setattr
+#define __NR_sched_setattr -2
+#endif
+
+extern struct rv_monitor rv_deadline;
+
+static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se)
+{
+	if (WARN_ONCE(dl_se->dl_server, "Call this only on a DL task!"))
+		return NULL;
+	return container_of(dl_se, struct task_struct, dl);
+}
+
+#ifdef CONFIG_RT_MUTEXES
+static inline struct sched_dl_entity *pi_of(struct sched_dl_entity *dl_se)
+{
+	return dl_se->pi_se;
+}
+
+static inline bool is_dl_boosted(struct sched_dl_entity *dl_se)
+{
+	return pi_of(dl_se) != dl_se;
+}
+#else /* !CONFIG_RT_MUTEXES: */
+static inline struct sched_dl_entity *pi_of(struct sched_dl_entity *dl_se)
+{
+	return dl_se;
+}
+
+static inline bool is_dl_boosted(struct sched_dl_entity *dl_se)
+{
+	return false;
+}
+#endif /* !CONFIG_RT_MUTEXES */
+
+static inline bool dl_is_implicit(struct sched_dl_entity *dl_se)
+{
+	return dl_se->dl_deadline == dl_se->dl_period;
+}
+
+/*
+ * If both have dummy values, the syscalls are not supported and we don't even
+ * need to register the handler.
+ */
+static inline bool should_skip_syscall_handle(void)
+{
+	return __NR_sched_setattr < 0 && __NR_sched_setscheduler < 0;
+}
+
+/*
+ * Use negative numbers for the server.
+ * Currently only one fair server per CPU, may change in the future.
+ */
+#define fair_server_id(cpu) (-cpu)
+/*
+ * Get a unique id used for dl entities
+ *
+ * The cpu is not required for tasks as the pid is used there, if this function
+ * is called on a dl_se that for sure corresponds to a task, DL_TASK can be
+ * used in place of cpu.
+ * We need the cpu for servers as it is provided in the tracepoint and we
+ * cannot easily retrieve it from the dl_se (requires the struct rq definition).
+ */
+static inline int get_entity_id(struct sched_dl_entity *dl_se, int cpu)
+{
+	if (dl_se->dl_server)
+		return fair_server_id(cpu);
+	return dl_task_of(dl_se)->pid;
+}
+
+/* Expand id and target as arguments for da functions */
+#define EXPAND_ID(dl_se, cpu) get_entity_id(dl_se, cpu), dl_se
+
+/* Use this as the cpu in EXPAND_ID in case the dl_se is surely from a task */
+#define DL_TASK -1
+
+static inline int extract_params(struct pt_regs *regs, long id, struct task_struct **p)
+{
+	size_t size = offsetof(struct sched_attr, sched_nice);
+	struct sched_attr __user *uattr, attr;
+	int new_policy = -1, ret;
+	unsigned long args[6];
+	pid_t pid;
+
+	switch (id) {
+	case __NR_sched_setscheduler:
+		syscall_get_arguments(current, regs, args);
+		pid = args[0];
+		new_policy = args[1];
+		break;
+	case __NR_sched_setattr:
+		syscall_get_arguments(current, regs, args);
+		pid = args[0];
+		uattr = (void *)args[1];
+		/*
+		 * Just copy up to sched_flags, we are not interested after that
+		 */
+		ret = copy_struct_from_user(&attr, size, uattr, size);
+		if (ret)
+			return ret;
+		if (attr.sched_flags & SCHED_FLAG_KEEP_POLICY)
+			return -EINVAL;
+		new_policy = attr.sched_policy;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (!pid)
+		*p = current;
+	else {
+		/*
+		 * Required for find_task_by_vpid, make sure the caller doesn't
+		 * need to get_task_struct().
+		 */
+		guard(rcu)();
+		*p = find_task_by_vpid(pid);
+		if (unlikely(!p))
+			return -EINVAL;
+	}
+
+	return new_policy;
+}
+
+/* Helper functions requiring DA/HA utilities*/
+#ifdef RV_MON_TYPE
+
+/*
+ * get_fair_server - get the fair server associated to a task
+ *
+ * If the task is a boosted task, the server is available in the task_struct,
+ * otherwise grab the dl entity saved for the CPU where the task is enqueued.
+ * This function assumes the task is enqueued somewhere.
+ */
+static inline struct sched_dl_entity *get_fair_server(struct task_struct *tsk)
+{
+	if (tsk->dl_server)
+		return tsk->dl_server;
+	return da_get_target_by_id(fair_server_id(task_cpu(tsk)));
+}
+
+/*
+ * Initialise monitors for all tasks and pre-allocate the storage for servers.
+ * This is necessary since we don't have access to the servers here and
+ * allocation can cause deadlocks from their tracepoints. We can only fill
+ * pre-initialised storage from there.
+ */
+static inline int init_storage(void)
+{
+	struct task_struct *g, *p;
+	int cpu;
+
+	for_each_possible_cpu(cpu) {
+		if (!da_create_empty_storage(fair_server_id(cpu), GFP_KERNEL))
+			goto fail;
+	}
+
+	read_lock(&tasklist_lock);
+	for_each_process_thread(g, p) {
+		if (p->policy == SCHED_DEADLINE) {
+			if (!da_create_storage(EXPAND_ID(&p->dl, DL_TASK), NULL,
+					       GFP_KERNEL)) {
+				read_unlock(&tasklist_lock);
+				goto fail;
+			}
+		}
+	}
+	read_unlock(&tasklist_lock);
+	return 0;
+
+fail:
+	da_monitor_destroy();
+	return -ENOMEM;
+}
+
+static void handle_newtask(void *data, struct task_struct *task, unsigned long flags)
+{
+	/* Might be superfluous as tasks are not started with this policy.. */
+	if (task->policy == SCHED_DEADLINE)
+		da_create_storage(EXPAND_ID(&task->dl, DL_TASK), NULL, GFP_NOWAIT);
+}
+
+static void handle_exit(void *data, struct task_struct *p, bool group_dead)
+{
+	if (p->policy == SCHED_DEADLINE)
+		da_destroy_storage(get_entity_id(&p->dl, DL_TASK));
+}
+
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/Kconfig b/kernel/trace/rv/monitors/nomiss/Kconfig
new file mode 100644
index 000000000000..e1886c3a0dd9
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_NOMISS
+	depends on RV
+	depends on HAVE_SYSCALL_TRACEPOINTS
+	depends on RV_MON_DEADLINE
+	default y
+	select HA_MON_EVENTS_ID
+	bool "nomiss monitor"
+	help
+	  Monitor to ensure dl entities run to completion before their deadiline.
+	  This monitor is part of the deadline monitors collection.
+
+	  For further information, see:
+	    Documentation/trace/rv/monitor_deadline.rst
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
new file mode 100644
index 000000000000..9bc8ab60d8dc
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -0,0 +1,283 @@
+// 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 "nomiss"
+
+#include <uapi/linux/sched/types.h>
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <trace/events/task.h>
+#include <rv_trace.h>
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+#define HA_TIMER_TYPE HA_TIMER_WHEEL
+/* The start condition is on sched_switch, it's dangerous to allocate there */
+#define DA_SKIP_AUTO_ALLOC
+typedef struct sched_dl_entity *monitor_target;
+#include "nomiss.h"
+#include <rv/ha_monitor.h>
+#include <monitors/deadline/deadline.h>
+
+/*
+ * User configurable deadline threshold. If the total utilisation of deadline
+ * tasks is larger than 1, they are only guaranteed bounded tardiness. See
+ * Documentation/scheduler/sched-deadline.rst for more details.
+ * The minimum tardiness without sched_feat(HRTICK_DL) is 1 tick to accommodate
+ * for throttle enforced on the next tick.
+ */
+static u64 deadline_thresh = TICK_NSEC;
+module_param(deadline_thresh, ullong, 0644);
+#define DEADLINE_NS(ha_mon) (pi_of(ha_get_target(ha_mon))->dl_deadline + deadline_thresh)
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_nomiss env, u64 time_ns)
+{
+	if (env == clk_nomiss)
+		return ha_get_clk_ns(ha_mon, env, time_ns);
+	else if (env == is_constr_dl_nomiss)
+		return !dl_is_implicit(ha_get_target(ha_mon));
+	else if (env == is_defer_nomiss)
+		return ha_get_target(ha_mon)->dl_defer;
+	return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_nomiss env, u64 time_ns)
+{
+	if (env == clk_nomiss)
+		ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+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 == ready_nomiss)
+		return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+	else if (curr_state == running_nomiss)
+		return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
+	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)
+{
+	bool res = true;
+
+	if (curr_state == idle_nomiss && event == dl_replenish_nomiss)
+		ha_reset_env(ha_mon, clk_nomiss, time_ns);
+	else if (curr_state == ready_nomiss && event == dl_replenish_nomiss)
+		ha_reset_env(ha_mon, clk_nomiss, time_ns);
+	else if (curr_state == ready_nomiss && event == dl_throttle_nomiss)
+		res = ha_get_env(ha_mon, is_defer_nomiss, time_ns) == 1ull;
+	else if (curr_state == running_nomiss && event == dl_replenish_nomiss)
+		ha_reset_env(ha_mon, clk_nomiss, time_ns);
+	else if (curr_state == sleeping_nomiss && event == dl_replenish_nomiss)
+		ha_reset_env(ha_mon, clk_nomiss, time_ns);
+	else if (curr_state == sleeping_nomiss && event == dl_throttle_nomiss)
+		res = ha_get_env(ha_mon, is_constr_dl_nomiss, time_ns) == 1ull;
+	else if (curr_state == throttled_nomiss && event == dl_replenish_nomiss)
+		ha_reset_env(ha_mon, clk_nomiss, 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 != dl_replenish_nomiss)
+		return;
+	if (next_state == ready_nomiss)
+		ha_start_timer_ns(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
+	else if (next_state == running_nomiss)
+		ha_start_timer_ns(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
+	else if (curr_state == ready_nomiss)
+		ha_cancel_timer(ha_mon);
+	else if (curr_state == running_nomiss)
+		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;
+
+	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;
+
+	ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+	return true;
+}
+
+static void handle_dl_replenish(void *data, struct sched_dl_entity *dl, int cpu)
+{
+	da_handle_event(EXPAND_ID(dl, cpu), dl_replenish_nomiss);
+}
+
+static void handle_dl_throttle(void *data, struct sched_dl_entity *dl, int cpu)
+{
+	da_handle_event(EXPAND_ID(dl, cpu), dl_throttle_nomiss);
+}
+
+static void handle_dl_server_start(void *data, struct sched_dl_entity *dl, int cpu)
+{
+	da_handle_event(EXPAND_ID(dl, cpu), dl_server_start_nomiss);
+}
+
+static void handle_dl_server_stop(void *data, struct sched_dl_entity *dl, int cpu, bool hard)
+{
+	/*
+	 * This isn't the standard use of da_handle_start_run_event since those
+	 * events cannot only occur from the initial states.
+	 * It is fine to use here because they always bring to a known state
+	 * and the fact we "pretend" the transition starts from the initial
+	 * state has no side effect.
+	 * dl_server_idle_nomiss is an even more special case as it can also be
+	 * triggered by the CPU going idle, only that can lead to other states.
+	 */
+	if (hard)
+		da_handle_start_run_event(EXPAND_ID(dl, cpu), dl_server_stop_nomiss);
+	else
+		da_handle_start_run_event(EXPAND_ID(dl, cpu), dl_server_idle_nomiss);
+}
+
+static void handle_sched_switch(void *data, bool preempt,
+				struct task_struct *prev,
+				struct task_struct *next,
+				unsigned int prev_state)
+{
+	struct sched_dl_entity *dl;
+	int cpu = task_cpu(next);
+
+	if (prev_state != TASK_RUNNING && !preempt && prev->policy == SCHED_DEADLINE)
+		da_handle_event(EXPAND_ID(&prev->dl, cpu), sched_switch_suspend_nomiss);
+	if (next->policy == SCHED_DEADLINE)
+		da_handle_start_run_event(EXPAND_ID(&next->dl, cpu), sched_switch_in_nomiss);
+
+	/*
+	 * The server is available in next only if the next task is boosted,
+	 * otherwise we need to retrieve it.
+	 * Here the server continues in the state running/armed until actually
+	 * stopped, this works since we continue expecting a throttle.
+	 */
+	dl = get_fair_server(next);
+	if (!dl)
+		return;
+	if (next->dl_server)
+		da_handle_start_run_event(EXPAND_ID(next->dl_server, cpu), sched_switch_in_nomiss);
+	else if (is_idle_task(next))
+		da_handle_event(EXPAND_ID(dl, cpu), dl_server_idle_nomiss);
+}
+
+static void handle_syscall(void *data, struct pt_regs *regs, long id)
+{
+	struct task_struct *p;
+	int new_policy = -1;
+
+	new_policy = extract_params(regs, id, &p);
+	if (new_policy < 0 || new_policy == p->policy)
+		return;
+	if (p->policy == SCHED_DEADLINE)
+		da_reset(EXPAND_ID(&p->dl, DL_TASK));
+	else if (new_policy == SCHED_DEADLINE)
+		da_create_conditional(EXPAND_ID(&p->dl, DL_TASK), GFP_NOWAIT);
+}
+
+static void handle_sched_wakeup(void *data, struct task_struct *tsk)
+{
+	if (tsk->policy == SCHED_DEADLINE)
+		da_handle_event(EXPAND_ID(&tsk->dl, task_cpu(tsk)), sched_wakeup_nomiss);
+}
+
+static int enable_nomiss(void)
+{
+	int retval;
+
+	retval = da_monitor_init();
+	if (retval)
+		return retval;
+
+	retval = init_storage();
+	if (retval)
+		return retval;
+	rv_attach_trace_probe("nomiss", sched_dl_replenish_tp, handle_dl_replenish);
+	rv_attach_trace_probe("nomiss", sched_dl_throttle_tp, handle_dl_throttle);
+	rv_attach_trace_probe("nomiss", sched_dl_server_start_tp, handle_dl_server_start);
+	rv_attach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
+	rv_attach_trace_probe("nomiss", sched_switch, handle_sched_switch);
+	rv_attach_trace_probe("nomiss", sched_wakeup, handle_sched_wakeup);
+	if (!should_skip_syscall_handle())
+		rv_attach_trace_probe("nomiss", sys_enter, handle_syscall);
+	rv_attach_trace_probe("nomiss", task_newtask, handle_newtask);
+	rv_attach_trace_probe("nomiss", sched_process_exit, handle_exit);
+
+	return 0;
+}
+
+static void disable_nomiss(void)
+{
+	rv_this.enabled = 0;
+
+	/* Those are RCU writers, detach earlier hoping to close a bit faster */
+	rv_detach_trace_probe("nomiss", task_newtask, handle_newtask);
+	rv_detach_trace_probe("nomiss", sched_process_exit, handle_exit);
+	if (!should_skip_syscall_handle())
+		rv_detach_trace_probe("nomiss", sys_enter, handle_syscall);
+
+	rv_detach_trace_probe("nomiss", sched_dl_replenish_tp, handle_dl_replenish);
+	rv_detach_trace_probe("nomiss", sched_dl_throttle_tp, handle_dl_throttle);
+	rv_detach_trace_probe("nomiss", sched_dl_server_start_tp, handle_dl_server_start);
+	rv_detach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
+	rv_detach_trace_probe("nomiss", sched_switch, handle_sched_switch);
+	rv_detach_trace_probe("nomiss", sched_wakeup, handle_sched_wakeup);
+
+	da_monitor_destroy();
+}
+
+static struct rv_monitor rv_this = {
+	.name = "nomiss",
+	.description = "dl entities run to completion before their deadiline.",
+	.enable = enable_nomiss,
+	.disable = disable_nomiss,
+	.reset = da_monitor_reset_all,
+	.enabled = 0,
+};
+
+static int __init register_nomiss(void)
+{
+	return rv_register_monitor(&rv_this, &rv_deadline);
+}
+
+static void __exit unregister_nomiss(void)
+{
+	rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_nomiss);
+module_exit(unregister_nomiss);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadiline.");
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.h b/kernel/trace/rv/monitors/nomiss/nomiss.h
new file mode 100644
index 000000000000..2cbfdbbc900f
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of nomiss automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME nomiss
+
+enum states_nomiss {
+	ready_nomiss,
+	idle_nomiss,
+	running_nomiss,
+	sleeping_nomiss,
+	throttled_nomiss,
+	state_max_nomiss,
+};
+
+#define INVALID_STATE state_max_nomiss
+
+enum events_nomiss {
+	dl_replenish_nomiss,
+	dl_server_idle_nomiss,
+	dl_server_start_nomiss,
+	dl_server_stop_nomiss,
+	dl_throttle_nomiss,
+	sched_switch_in_nomiss,
+	sched_switch_suspend_nomiss,
+	sched_wakeup_nomiss,
+	event_max_nomiss,
+};
+
+enum envs_nomiss {
+	clk_nomiss,
+	is_constr_dl_nomiss,
+	is_defer_nomiss,
+	env_max_nomiss,
+	env_max_stored_nomiss = is_constr_dl_nomiss,
+};
+
+_Static_assert(env_max_stored_nomiss <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_nomiss {
+	char *state_names[state_max_nomiss];
+	char *event_names[event_max_nomiss];
+	char *env_names[env_max_nomiss];
+	unsigned char function[state_max_nomiss][event_max_nomiss];
+	unsigned char initial_state;
+	bool final_states[state_max_nomiss];
+};
+
+static const struct automaton_nomiss automaton_nomiss = {
+	.state_names = {
+		"ready",
+		"idle",
+		"running",
+		"sleeping",
+		"throttled",
+	},
+	.event_names = {
+		"dl_replenish",
+		"dl_server_idle",
+		"dl_server_start",
+		"dl_server_stop",
+		"dl_throttle",
+		"sched_switch_in",
+		"sched_switch_suspend",
+		"sched_wakeup",
+	},
+	.env_names = {
+		"clk",
+		"is_constr_dl",
+		"is_defer",
+	},
+	.function = {
+		{
+			ready_nomiss,
+			idle_nomiss,
+			ready_nomiss,
+			sleeping_nomiss,
+			throttled_nomiss,
+			running_nomiss,
+			INVALID_STATE,
+			ready_nomiss,
+		},
+		{
+			ready_nomiss,
+			idle_nomiss,
+			INVALID_STATE,
+			INVALID_STATE,
+			throttled_nomiss,
+			running_nomiss,
+			INVALID_STATE,
+			INVALID_STATE,
+		},
+		{
+			running_nomiss,
+			idle_nomiss,
+			INVALID_STATE,
+			sleeping_nomiss,
+			throttled_nomiss,
+			running_nomiss,
+			sleeping_nomiss,
+			running_nomiss,
+		},
+		{
+			ready_nomiss,
+			sleeping_nomiss,
+			sleeping_nomiss,
+			INVALID_STATE,
+			throttled_nomiss,
+			INVALID_STATE,
+			INVALID_STATE,
+			ready_nomiss,
+		},
+		{
+			ready_nomiss,
+			throttled_nomiss,
+			throttled_nomiss,
+			INVALID_STATE,
+			throttled_nomiss,
+			INVALID_STATE,
+			throttled_nomiss,
+			throttled_nomiss,
+		},
+	},
+	.initial_state = ready_nomiss,
+	.final_states = { 1, 0, 0, 0, 0 },
+};
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_trace.h b/kernel/trace/rv/monitors/nomiss/nomiss_trace.h
new file mode 100644
index 000000000000..42e7efaca4e7
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_NOMISS
+DEFINE_EVENT(event_da_monitor_id, event_nomiss,
+	     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_nomiss,
+	     TP_PROTO(int id, char *state, char *event),
+	     TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_nomiss,
+	     TP_PROTO(int id, char *state, char *event, char *env),
+	     TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_NOMISS */
diff --git a/kernel/trace/rv/monitors/throttle/Kconfig b/kernel/trace/rv/monitors/throttle/Kconfig
new file mode 100644
index 000000000000..d9bd2dc903cd
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_THROTTLE
+	depends on RV
+	depends on HAVE_SYSCALL_TRACEPOINTS
+	depends on RV_MON_DEADLINE
+	default y
+	select HA_MON_EVENTS_ID
+	bool "throttle monitor"
+	help
+	  Monitor to ensure dl entities are throttled when they use up their runtime.
+	  This monitor is part of the deadline monitors collection.
+
+	  For further information, see:
+	    Documentation/trace/rv/monitor_deadline.rst
diff --git a/kernel/trace/rv/monitors/throttle/throttle.c b/kernel/trace/rv/monitors/throttle/throttle.c
new file mode 100644
index 000000000000..2eceb11335c5
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/throttle.c
@@ -0,0 +1,253 @@
+// 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 "throttle"
+
+#include <uapi/linux/sched/types.h>
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <trace/events/task.h>
+#include <rv_trace.h>
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+#define HA_TIMER_TYPE HA_TIMER_WHEEL
+/* The start condition is on sched_switch, it's dangerous to allocate there */
+#define DA_SKIP_AUTO_ALLOC
+typedef struct sched_dl_entity *monitor_target;
+#include "throttle.h"
+#include <rv/ha_monitor.h>
+#include <monitors/deadline/deadline.h>
+
+#define THROTTLED_TIME_NS TICK_NSEC
+/* with sched_feat(HRTICK_DL) the threshold can be lower */
+#define RUNTIME_THRESH TICK_NSEC
+
+static inline u64 runtime_left_ns(struct ha_monitor *ha_mon)
+{
+	return pi_of(ha_get_target(ha_mon))->runtime + RUNTIME_THRESH;
+}
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_throttle env, u64 time_ns)
+{
+	if (env == clk_throttle)
+		return ha_get_clk_ns(ha_mon, env, time_ns);
+	else if (env == is_constr_dl_throttle)
+		return !dl_is_implicit(ha_get_target(ha_mon));
+	else if (env == yielded_throttle)
+		return ha_get_target(ha_mon)->dl_yielded;
+	return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_throttle env, u64 time_ns)
+{
+	if (env == clk_throttle)
+		ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+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 == running_throttle)
+		return ha_check_invariant_ns(ha_mon, clk_throttle, time_ns);
+	else if (curr_state == throttled_throttle)
+		return ha_check_invariant_ns(ha_mon, clk_throttle, time_ns);
+	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 == armed_throttle && event == sched_switch_in_throttle)
+		ha_reset_env(ha_mon, clk_throttle, time_ns);
+	else if (curr_state == armed_throttled_throttle && event == dl_throttle_throttle)
+		res = ha_get_env(ha_mon, yielded_throttle, time_ns) == 1ull;
+	else if (curr_state == preempted_throttle && event == dl_throttle_throttle)
+		res = ha_get_env(ha_mon, is_constr_dl_throttle, time_ns) == 1ull;
+	else if (curr_state == preempted_throttle && event == sched_switch_in_throttle)
+		ha_reset_env(ha_mon, clk_throttle, time_ns);
+	else if (curr_state == running_throttle && event == dl_replenish_throttle)
+		ha_reset_env(ha_mon, clk_throttle, time_ns);
+	else if (curr_state == running_throttle && event == dl_throttle_throttle)
+		ha_reset_env(ha_mon, clk_throttle, time_ns);
+	else if (curr_state == throttled_throttle && event == dl_replenish_throttle)
+		ha_reset_env(ha_mon, clk_throttle, 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 != dl_replenish_throttle)
+		return;
+	if (next_state == running_throttle)
+		ha_start_timer_ns(ha_mon, clk_throttle, runtime_left_ns(ha_mon), time_ns);
+	else if (next_state == throttled_throttle)
+		ha_start_timer_ns(ha_mon, clk_throttle, THROTTLED_TIME_NS, time_ns);
+	else if (curr_state == running_throttle)
+		ha_cancel_timer(ha_mon);
+	else if (curr_state == throttled_throttle)
+		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_dl_replenish(void *data, struct sched_dl_entity *dl, int cpu)
+{
+	da_handle_event(EXPAND_ID(dl, cpu), dl_replenish_throttle);
+}
+
+static void handle_dl_throttle(void *data, struct sched_dl_entity *dl, int cpu)
+{
+	da_handle_event(EXPAND_ID(dl, cpu), dl_throttle_throttle);
+}
+
+static void handle_dl_server_stop(void *data, struct sched_dl_entity *dl, int cpu, bool hard)
+{
+	if (hard)
+		da_handle_event(EXPAND_ID(dl, cpu), sched_switch_out_throttle);
+}
+
+static void handle_sched_switch(void *data, bool preempt,
+				struct task_struct *prev,
+				struct task_struct *next,
+				unsigned int prev_state)
+{
+	struct sched_dl_entity *dl;
+	int cpu = task_cpu(next);
+
+	if (prev->policy == SCHED_DEADLINE)
+		da_handle_event(EXPAND_ID(&prev->dl, cpu), sched_switch_out_throttle);
+	if (next->policy == SCHED_DEADLINE)
+		da_handle_start_event(EXPAND_ID(&next->dl, cpu), sched_switch_in_throttle);
+
+	/*
+	 * The server is available in next only if the next task is boosted,
+	 * otherwise we need to retrieve it.
+	 * Here the server continues in the state running/armed until actually
+	 * stopped, this works since we continue expecting a throttle.
+	 */
+	dl = get_fair_server(next);
+	if (!dl)
+		return;
+	if (next->dl_server)
+		da_handle_start_event(EXPAND_ID(next->dl_server, cpu), sched_switch_in_throttle);
+	else if (is_idle_task(next) || next->policy == SCHED_NORMAL)
+		da_handle_event(EXPAND_ID(dl, cpu), dl_defer_arm_throttle);
+}
+
+static void handle_syscall(void *data, struct pt_regs *regs, long id)
+{
+	struct task_struct *p;
+	int new_policy = -1;
+
+	new_policy = extract_params(regs, id, &p);
+	if (new_policy < 0 || new_policy == p->policy)
+		return;
+	if (p->policy == SCHED_DEADLINE) {
+		da_reset(EXPAND_ID(&p->dl, DL_TASK));
+		/*
+		 * When a task changes from SCHED_DEADLINE to SCHED_NORMAL, the
+		 * runtime after the change is counted in the fair server.
+		 */
+		if (new_policy == SCHED_NORMAL) {
+			struct sched_dl_entity *dl = get_fair_server(p);
+
+			if (!dl || !p->on_cpu)
+				return;
+			da_handle_event(EXPAND_ID(dl, task_cpu(p)), dl_defer_arm_throttle);
+		}
+	} else if (new_policy == SCHED_DEADLINE) {
+		da_create_conditional(EXPAND_ID(&p->dl, DL_TASK), GFP_NOWAIT);
+	}
+}
+
+static int enable_throttle(void)
+{
+	int retval;
+
+	retval = da_monitor_init();
+	if (retval)
+		return retval;
+
+	retval = init_storage();
+	if (retval)
+		return retval;
+	rv_attach_trace_probe("throttle", sched_dl_replenish_tp, handle_dl_replenish);
+	rv_attach_trace_probe("throttle", sched_dl_throttle_tp, handle_dl_throttle);
+	rv_attach_trace_probe("throttle", sched_switch, handle_sched_switch);
+	if (!should_skip_syscall_handle())
+		rv_attach_trace_probe("throttle", sys_enter, handle_syscall);
+	rv_attach_trace_probe("throttle", task_newtask, handle_newtask);
+	rv_attach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
+	rv_attach_trace_probe("throttle", sched_process_exit, handle_exit);
+
+	return 0;
+}
+
+static void disable_throttle(void)
+{
+	rv_this.enabled = 0;
+
+	/* Those are RCU writers, detach earlier hoping to close a bit faster */
+	rv_detach_trace_probe("throttle", task_newtask, handle_newtask);
+	rv_detach_trace_probe("throttle", sched_process_exit, handle_exit);
+	if (!should_skip_syscall_handle())
+		rv_detach_trace_probe("throttle", sys_enter, handle_syscall);
+
+	rv_detach_trace_probe("throttle", sched_dl_replenish_tp, handle_dl_replenish);
+	rv_detach_trace_probe("throttle", sched_dl_throttle_tp, handle_dl_throttle);
+	rv_detach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
+	rv_detach_trace_probe("throttle", sched_switch, handle_sched_switch);
+
+	da_monitor_destroy();
+}
+
+static struct rv_monitor rv_this = {
+	.name = "throttle",
+	.description = "throttle dl entities when they use up their runtime.",
+	.enable = enable_throttle,
+	.disable = disable_throttle,
+	.reset = da_monitor_reset_all,
+	.enabled = 0,
+};
+
+static int __init register_throttle(void)
+{
+	return rv_register_monitor(&rv_this, &rv_deadline);
+}
+
+static void __exit unregister_throttle(void)
+{
+	rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_throttle);
+module_exit(unregister_throttle);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("throttle: throttle dl entities when they use up their runtime.");
diff --git a/kernel/trace/rv/monitors/throttle/throttle.h b/kernel/trace/rv/monitors/throttle/throttle.h
new file mode 100644
index 000000000000..17bfa89ea26b
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/throttle.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of throttle automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME throttle
+
+enum states_throttle {
+	running_throttle,
+	armed_throttle,
+	armed_throttled_throttle,
+	preempted_throttle,
+	preempted_throttled_throttle,
+	throttled_throttle,
+	state_max_throttle,
+};
+
+#define INVALID_STATE state_max_throttle
+
+enum events_throttle {
+	dl_defer_arm_throttle,
+	dl_replenish_throttle,
+	dl_throttle_throttle,
+	sched_switch_in_throttle,
+	sched_switch_out_throttle,
+	event_max_throttle,
+};
+
+enum envs_throttle {
+	clk_throttle,
+	is_constr_dl_throttle,
+	yielded_throttle,
+	env_max_throttle,
+	env_max_stored_throttle = is_constr_dl_throttle,
+};
+
+_Static_assert(env_max_stored_throttle <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_throttle {
+	char *state_names[state_max_throttle];
+	char *event_names[event_max_throttle];
+	char *env_names[env_max_throttle];
+	unsigned char function[state_max_throttle][event_max_throttle];
+	unsigned char initial_state;
+	bool final_states[state_max_throttle];
+};
+
+static const struct automaton_throttle automaton_throttle = {
+	.state_names = {
+		"running",
+		"armed",
+		"armed_throttled",
+		"preempted",
+		"preempted_throttled",
+		"throttled",
+	},
+	.event_names = {
+		"dl_defer_arm",
+		"dl_replenish",
+		"dl_throttle",
+		"sched_switch_in",
+		"sched_switch_out",
+	},
+	.env_names = {
+		"clk",
+		"is_constr_dl",
+		"yielded",
+	},
+	.function = {
+		{
+			armed_throttle,
+			running_throttle,
+			throttled_throttle,
+			running_throttle,
+			preempted_throttle,
+		},
+		{
+			armed_throttle,
+			armed_throttle,
+			armed_throttled_throttle,
+			running_throttle,
+			preempted_throttle,
+		},
+		{
+			armed_throttled_throttle,
+			armed_throttle,
+			armed_throttled_throttle,
+			INVALID_STATE,
+			preempted_throttled_throttle,
+		},
+		{
+			armed_throttle,
+			preempted_throttle,
+			preempted_throttled_throttle,
+			running_throttle,
+			preempted_throttle,
+		},
+		{
+			armed_throttled_throttle,
+			preempted_throttle,
+			INVALID_STATE,
+			INVALID_STATE,
+			preempted_throttled_throttle,
+		},
+		{
+			armed_throttled_throttle,
+			running_throttle,
+			INVALID_STATE,
+			INVALID_STATE,
+			preempted_throttled_throttle,
+		},
+	},
+	.initial_state = running_throttle,
+	.final_states = { 1, 0, 0, 0, 0, 0 },
+};
diff --git a/kernel/trace/rv/monitors/throttle/throttle_trace.h b/kernel/trace/rv/monitors/throttle/throttle_trace.h
new file mode 100644
index 000000000000..7e376d3aec60
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/throttle_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_THROTTLE
+DEFINE_EVENT(event_da_monitor_id, event_throttle,
+	     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_throttle,
+	     TP_PROTO(int id, char *state, char *event),
+	     TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_throttle,
+	     TP_PROTO(int id, char *state, char *event, char *env),
+	     TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_THROTTLE */
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 8ac4f315a627..687f42ba2534 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -188,6 +188,8 @@ DECLARE_EVENT_CLASS(error_env_da_monitor_id,
 );
 
 #include <monitors/stall/stall_trace.h>
+#include <monitors/nomiss/nomiss_trace.h>
+#include <monitors/throttle/throttle_trace.h>
 // Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
 
 #endif
diff --git a/tools/verification/models/deadline/nomiss.dot b/tools/verification/models/deadline/nomiss.dot
new file mode 100644
index 000000000000..c52970d6a8f7
--- /dev/null
+++ b/tools/verification/models/deadline/nomiss.dot
@@ -0,0 +1,39 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = circle] "idle"};
+	{node [shape = plaintext, style=invis, label=""] "__init_ready"};
+	{node [shape = doublecircle] "ready"};
+	{node [shape = circle] "ready"};
+	{node [shape = circle] "running"};
+	{node [shape = circle] "sleeping"};
+	{node [shape = circle] "throttled"};
+	"__init_ready" -> "ready";
+	"idle" [label = "idle"];
+	"idle" -> "idle" [ label = "dl_server_idle" ];
+	"idle" -> "ready" [ label = "dl_replenish;reset(clk)" ];
+	"idle" -> "running" [ label = "sched_switch_in" ];
+	"idle" -> "throttled" [ label = "dl_throttle" ];
+	"ready" [label = "ready\nclk < DEADLINE_NS()", color = green3];
+	"ready" -> "idle" [ label = "dl_server_idle" ];
+	"ready" -> "ready" [ label = "sched_wakeup\ndl_server_start\ndl_replenish;reset(clk)" ];
+	"ready" -> "running" [ label = "sched_switch_in" ];
+	"ready" -> "sleeping" [ label = "dl_server_stop" ];
+	"ready" -> "throttled" [ label = "dl_throttle;is_defer == 1" ];
+	"running" [label = "running\nclk < DEADLINE_NS()"];
+	"running" -> "idle" [ label = "dl_server_idle" ];
+	"running" -> "running" [ label = "dl_replenish;reset(clk)\nsched_switch_in\nsched_wakeup" ];
+	"running" -> "sleeping" [ label = "sched_switch_suspend\ndl_server_stop" ];
+	"running" -> "throttled" [ label = "dl_throttle" ];
+	"sleeping" [label = "sleeping"];
+	"sleeping" -> "ready" [ label = "sched_wakeup\ndl_replenish;reset(clk)" ];
+	"sleeping" -> "sleeping" [ label = "dl_server_idle\ndl_server_start" ];
+	"sleeping" -> "throttled" [ label = "dl_throttle;is_constr_dl == 1" ];
+	"throttled" [label = "throttled"];
+	"throttled" -> "ready" [ label = "dl_replenish;reset(clk)" ];
+	"throttled" -> "throttled" [ label = "sched_switch_suspend\nsched_wakeup\ndl_server_start\ndl_server_idle\ndl_throttle" ];
+	{ rank = min ;
+		"__init_ready";
+		"ready";
+	}
+}
diff --git a/tools/verification/models/deadline/throttle.dot b/tools/verification/models/deadline/throttle.dot
new file mode 100644
index 000000000000..b68b054a635a
--- /dev/null
+++ b/tools/verification/models/deadline/throttle.dot
@@ -0,0 +1,44 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = circle] "armed"};
+	{node [shape = circle] "armed_throttled"};
+	{node [shape = circle] "preempted"};
+	{node [shape = circle] "preempted_throttled"};
+	{node [shape = plaintext, style=invis, label=""] "__init_running"};
+	{node [shape = doublecircle] "running"};
+	{node [shape = circle] "running"};
+	{node [shape = circle] "throttled"};
+	"__init_running" -> "running";
+	"armed" [label = "armed"];
+	"armed" -> "armed" [ label = "dl_replenish\ndl_defer_arm" ];
+	"armed" -> "armed_throttled" [ label = "dl_throttle" ];
+	"armed" -> "preempted" [ label = "sched_switch_out" ];
+	"armed" -> "running" [ label = "sched_switch_in;reset(clk)" ];
+	"armed_throttled" [label = "armed_throttled"];
+	"armed_throttled" -> "armed" [ label = "dl_replenish" ];
+	"armed_throttled" -> "armed_throttled" [ label = "dl_defer_arm\ndl_throttle;yielded==1" ];
+	"armed_throttled" -> "preempted_throttled" [ label = "sched_switch_out" ];
+	"preempted" [label = "preempted"];
+	"preempted" -> "armed" [ label = "dl_defer_arm" ];
+	"preempted" -> "preempted" [ label = "dl_replenish\nsched_switch_out" ];
+	"preempted" -> "preempted_throttled" [ label = "dl_throttle;is_constr_dl == 1" ];
+	"preempted" -> "running" [ label = "sched_switch_in;reset(clk)" ];
+	"preempted_throttled" [label = "preempted_throttled"];
+	"preempted_throttled" -> "armed_throttled" [ label = "dl_defer_arm" ];
+	"preempted_throttled" -> "preempted" [ label = "dl_replenish" ];
+	"preempted_throttled" -> "preempted_throttled" [ label = "sched_switch_out" ];
+	"running" [label = "running\nclk < runtime_left_ns()", color = green3];
+	"running" -> "armed" [ label = "dl_defer_arm" ];
+	"running" -> "preempted" [ label = "sched_switch_out" ];
+	"running" -> "running" [ label = "dl_replenish;reset(clk)\nsched_switch_in" ];
+	"running" -> "throttled" [ label = "dl_throttle;reset(clk)" ];
+	"throttled" [label = "throttled\nclk < THROTTLED_TIME_NS"];
+	"throttled" -> "armed_throttled" [ label = "dl_defer_arm" ];
+	"throttled" -> "preempted_throttled" [ label = "sched_switch_out" ];
+	"throttled" -> "running" [ label = "dl_replenish;reset(clk)" ];
+	{ rank = min ;
+		"__init_running";
+		"running";
+	}
+}
-- 
2.51.0


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

* Re: [PATCH v2 16/20] sched: Export hidden tracepoints to modules
  2025-09-19 14:09 ` [PATCH v2 16/20] sched: Export hidden tracepoints to modules Gabriele Monaco
@ 2025-09-19 15:37   ` Steven Rostedt
  2025-09-19 17:07     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Steven Rostedt @ 2025-09-19 15:37 UTC (permalink / raw)
  To: Gabriele Monaco
  Cc: linux-kernel, Ingo Molnar, Peter Zijlstra, Nam Cao, Tomas Glozar,
	Juri Lelli, Clark Williams, John Kacur, linux-trace-kernel

On Fri, 19 Sep 2025 16:09:50 +0200
Gabriele Monaco <gmonaco@redhat.com> wrote:

> The tracepoints sched_entry, sched_exit and sched_set_need_resched
> are not exported to tracefs as trace events, this allows only kernel
> code to access them. Helper modules like [1] can be used to still have
> the tracepoints available to ftrace for debugging purposes, but they do
> rely on the tracepoints being exported.
> 
> Export the 3 not exported tracepoints.
> Note that sched_set_state is already exported as the macro is called
> from modules.
> 
> [1] - https://github.com/qais-yousef/sched_tp.git
> 
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Note, for all the scheduler changes, we need an acked-by from the scheduler
maintainers.

After you have acks and are satisfied with the code (hopefully someone else
can review them), you can send me a pull request to be added into linux-next.
I'll still run them through my own tests.

Which BTW, there should be a bunch of tests in:

  tools/testing/selftests/verification/

(which doesn't yet exist).

-- Steve


> ---
>  kernel/sched/core.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index ccba6fc3c3fe..334ff5b214d7 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -117,6 +117,9 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_cfs_tp);
>  EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_se_tp);
>  EXPORT_TRACEPOINT_SYMBOL_GPL(sched_update_nr_running_tp);
>  EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
>  
>  DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
>  


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

* Re: [PATCH v2 16/20] sched: Export hidden tracepoints to modules
  2025-09-19 15:37   ` Steven Rostedt
@ 2025-09-19 17:07     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-09-19 17:07 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: linux-kernel, Ingo Molnar, Peter Zijlstra, Nam Cao, Tomas Glozar,
	Juri Lelli, Clark Williams, John Kacur, linux-trace-kernel

2025-09-19T15:36:50Z Steven Rostedt <rostedt@goodmis.org>:

> On Fri, 19 Sep 2025 16:09:50 +0200
> Gabriele Monaco <gmonaco@redhat.com> wrote:
>
>> The tracepoints sched_entry, sched_exit and sched_set_need_resched
>> are not exported to tracefs as trace events, this allows only kernel
>> code to access them. Helper modules like [1] can be used to still have
>> the tracepoints available to ftrace for debugging purposes, but they do
>> rely on the tracepoints being exported.
>>
>> Export the 3 not exported tracepoints.
>> Note that sched_set_state is already exported as the macro is called
>> from modules.
>>
>> [1] - https://github.com/qais-yousef/sched_tp.git
>>
>> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
>
> Note, for all the scheduler changes, we need an acked-by from the scheduler
> maintainers.
>

Alright got it.

> After you have acks and are satisfied with the code (hopefully someone else
> can review them), you can send me a pull request to be added into linux-next.

Usually Nam does a very good job reviewing them.

> I'll still run them through my own tests.
>
> Which BTW, there should be a bunch of tests in:
>
>   tools/testing/selftests/verification/
>
> (which doesn't yet exist).

Yeah that's the next thing in my imaginary list, or perhaps something inspired on what's done in rtla.

Thanks,
Gabriele

>
> -- Steve
>
>
>> ---
>> kernel/sched/core.c | 3 +++
>> 1 file changed, 3 insertions(+)
>>
>> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
>> index ccba6fc3c3fe..334ff5b214d7 100644
>> --- a/kernel/sched/core.c
>> +++ b/kernel/sched/core.c
>> @@ -117,6 +117,9 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_cfs_tp);
>> EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_se_tp);
>> EXPORT_TRACEPOINT_SYMBOL_GPL(sched_update_nr_running_tp);
>> EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
>> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
>> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
>> +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
>>
>> DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
>>


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

* Re: [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros
  2025-09-19 14:09 ` [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros Gabriele Monaco
@ 2025-10-02  8:45   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  8:45 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> The da_monitor helper functions are generated from macros of the type:
>
> DECLARE_DA_FUNCTION(name, type) \
> static void da_func_x_##name(type arg) {} \
> static void da_func_y_##name(type arg) {} \
>
> This is good to minimise code duplication but the long macros made of
> skipped end of lines is rather hard to parse. Since functions are
> static, the advantage of naming them differently for each monitor is
> minimal.
>
> Refactor the da_monitor.h file to minimise macros, instead of declaring
> functions from macros, we simply declare them with the same name for all
> monitors (e.g. da_func_x) and for any remaining reference to the monitor
> name (e.g. tracepoints, enums, global variables) we use the CONCATENATE
> macro.
> In this way the file is much easier to maintain while keeping the same
> generality.
> Functions depending on the monitor types are now conditionally compiled
> according to the value of RV_MON_TYPE, which must be defined in the
> monitor source.
> The monitor type can be specified as in the original implementation,
> although it's best to keep the default implementation (unsigned char) as
> not all parts of code support larger data types, and likely there's no
> need.
>
> We keep the empty macro definitions to ease review of this change with
> diff tools, but cleanup is required.
>
> Also adapt existing monitors to keep the build working.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Farewell, macros. You won't be missed.

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 02/20] rv: Cleanup da_monitor after refactor
  2025-09-19 14:09 ` [PATCH v2 02/20] rv: Cleanup da_monitor after refactor Gabriele Monaco
@ 2025-10-02  8:49   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  8:49 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> Previous changes refactored the da_monitor header file to avoid using
> macros, however empty macros (e.g. DECLARE_DA_FUNCTION) were left to
> ease review with diff tools.
> Most macros also get the argument type which doesn't really have a
> purpose since states have their own enum and the storage in struct
> da_monitor is fixed to unsigned int.
>
> Remove empty and no longer required macros and substitute the type
> parameter with the appropriate enum.
> Additionally break long line and adjust the format overall.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types
  2025-09-19 14:09 ` [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types Gabriele Monaco
@ 2025-10-02  9:14   ` Nam Cao
  2025-10-02 11:30     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-02  9:14 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> +/*
> + * da_get_task - return the task associated to the monitor
> + */
> +static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
> +{
> +	return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon);
> +}

This function is not used? Maybe later in the series, let me keep looking..

>  static inline bool da_handle_start_event(enum events event)
>  {
> -	struct da_monitor *da_mon;
> -
>  	if (!da_monitor_enabled())
>  		return 0;

Can't this part be shared between different monitor types?

Same for the other handle functions.

Nam

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

* Re: [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring
  2025-09-19 14:09 ` [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
@ 2025-10-02  9:26   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  9:26 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Jonathan Corbet,
	linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:

> Previous changes refactored the da_monitor header file to avoid using
> macros. This implies a few changes in how to import and use da_monitor
> helpers:
>
>  DECLARE_DA_MON_<TYPE>(name, type) is substituted by
>  #define RV_MON_TYPE RV_MON_<TYPE>
>
>  da_handle_event_<name>() is substituted by
>  da_handle_event()
>
> Update the documentation to reflect the changes.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

This is probably best reviewed by someone without much RV background. As
I already know these information, I'm not sure whether the doc is
confusing to outsiders.

But from my point of view:
Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h
  2025-09-19 14:09 ` [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
@ 2025-10-02  9:34   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  9:34 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:

> Previous changes refactored the da_monitor header file to avoid using
> macros. This implies a few changes in how to import and use da_monitor
> helpers:
>
>  DECLARE_DA_MON_<TYPE>(name, type) is substituted by
>  #define RV_MON_TYPE RV_MON_<TYPE>
>
> Update the rvgen templates to reflect the changes.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types
  2025-09-19 14:09 ` [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types Gabriele Monaco
@ 2025-10-02  9:39   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  9:39 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:

> Functions in automata.py, dot2c.py and dot2k.py don't have type
> annotations and it can get complicated to remember how to use them.
>
> Add minimal type annotations.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma
  2025-09-19 14:09 ` [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma Gabriele Monaco
@ 2025-10-02  9:40   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-02  9:40 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:

> The header files generated by dot2c currently create enums for states
> and events assigning the first element to 0. This is superfluous as it
> happens automatically if no value is specified.
> Also it doesn't add a comma to the last enum elements, which slightly
> complicates the diff if states or events are added.
>
> Remove the assignment to 0 and add a comma to last elements, this
> simplifies the logic for the code generator.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

There are some minor changes which are not mentioned, but not a big deal:

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings
  2025-09-19 14:09 ` [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
@ 2025-10-02 11:03   ` Nam Cao
  2025-10-02 11:17     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-02 11:03 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:

> Currently the automata parser assumes event strings don't have any
> space, this stands true for event names, but can be a wrong assumption
> if we want to store other information in the event strings (e.g.
> constraints for hybrid automata).
>
> Adapt the parser logic to allow spaces in the event strings.

I probably misunderstand something, but isn't the description
misleading? After reading this description, I expect the patch to ignore
spaces or something similar. But from my understanding, the script only
allowed a single event, and this patch allows conditions as well.

Shouldn't this be squashed to the next patch?

Nam

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

* Re: [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings
  2025-10-02 11:03   ` Nam Cao
@ 2025-10-02 11:17     ` Gabriele Monaco
  2025-10-06 13:20       ` Nam Cao
  0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-02 11:17 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Thu, 2025-10-02 at 13:03 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> 
> > Currently the automata parser assumes event strings don't have any
> > space, this stands true for event names, but can be a wrong assumption
> > if we want to store other information in the event strings (e.g.
> > constraints for hybrid automata).
> > 
> > Adapt the parser logic to allow spaces in the event strings.
> 
> I probably misunderstand something, but isn't the description
> misleading? After reading this description, I expect the patch to ignore
> spaces or something similar. But from my understanding, the script only
> allowed a single event, and this patch allows conditions as well.

The script allows multiple events, all separated by \n, strictly speaking there
is nothing saying spaces are not allowed in event names, but the parser breaks
if there's any space.

This patch allows spaces in event names, conditions (separated by a ; ) are not
supported yet.

> Shouldn't this be squashed to the next patch?

I kept it separated to avoid pushing too many changes in the next one, which
mostly adds new functionality (and lines) instead of changing the current ones.

Apparently that didn't make it any clearer, and there isn't really any use case
needing event names with spaces, so if it looks cleaner to you I can just squash
it.

Thanks,
Gabriele


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

* Re: [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types
  2025-10-02  9:14   ` Nam Cao
@ 2025-10-02 11:30     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-02 11:30 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Thu, 2025-10-02 at 11:14 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > +/*
> > + * da_get_task - return the task associated to the monitor
> > + */
> > +static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
> > +{
> > +	return container_of(da_mon, struct task_struct,
> > rv[task_mon_slot].da_mon);
> > +}
> 
> This function is not used? Maybe later in the series, let me keep looking..

Right, this doesn't belong here, I should add it later..

> 
> >  static inline bool da_handle_start_event(enum events event)
> >  {
> > -	struct da_monitor *da_mon;
> > -
> >  	if (!da_monitor_enabled())
> >  		return 0;
> 
> Can't this part be shared between different monitor types?
> 
> Same for the other handle functions.

Mmh good point, I left it separate because the per-object monitors (later in the
series) do a bit more before calling the __da_handle_* functions (like some
potential allocation).

But it's probably not a big deal to let them do that in any case, considering
da_monitor_enabled() is false only during teardown or with monitoring off, and
in neither case a superfluous allocation should cause problems.

Thanks,
Gabriele


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

* Re: [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings
  2025-10-02 11:17     ` Gabriele Monaco
@ 2025-10-06 13:20       ` Nam Cao
  2025-10-06 15:22         ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-06 13:20 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> On Thu, 2025-10-02 at 13:03 +0200, Nam Cao wrote:
>> Gabriele Monaco <gmonaco@redhat.com> writes:
>> 
>> > Currently the automata parser assumes event strings don't have any
>> > space, this stands true for event names, but can be a wrong assumption
>> > if we want to store other information in the event strings (e.g.
>> > constraints for hybrid automata).
>> > 
>> > Adapt the parser logic to allow spaces in the event strings.
>> 
>> I probably misunderstand something, but isn't the description
>> misleading? After reading this description, I expect the patch to ignore
>> spaces or something similar. But from my understanding, the script only
>> allowed a single event, and this patch allows conditions as well.
>
> The script allows multiple events, all separated by \n, strictly speaking there
> is nothing saying spaces are not allowed in event names, but the parser breaks
> if there's any space.
>
> This patch allows spaces in event names, conditions (separated by a ; ) are not
> supported yet.
>
>> Shouldn't this be squashed to the next patch?
>
> I kept it separated to avoid pushing too many changes in the next one, which
> mostly adds new functionality (and lines) instead of changing the current ones.
>
> Apparently that didn't make it any clearer, and there isn't really any use case
> needing event names with spaces, so if it looks cleaner to you I can just squash
> it.

Nah, you can keep it. I was just confused. Now I looked again, it starts
to make sense.

Reviewed-by: Nam Cao <namcao@linutronix.de>

I am thinking about converting the entire thing to be ply-based (after
we are done with this series). It should make things easier to
follow. Would you object that?

Nam


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

* Re: [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings
  2025-10-06 13:20       ` Nam Cao
@ 2025-10-06 15:22         ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-06 15:22 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Mon, 2025-10-06 at 15:20 +0200, Nam Cao wrote:
> Nah, you can keep it. I was just confused. Now I looked again, it starts
> to make sense.
> 
> Reviewed-by: Nam Cao <namcao@linutronix.de>
> 

Alright, thanks!

> I am thinking about converting the entire thing to be ply-based (after
> we are done with this series). It should make things easier to
> follow. Would you object that?

I'm on board with that, I left out some other changes to the parser in light of
a major refactoring. I'd just need to see how this thing works but it really
seems the right thing to do.

Gabriele


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

* Re: [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata
  2025-09-19 14:09 ` [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
@ 2025-10-10 13:46   ` Nam Cao
  2025-10-13  8:33     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-10 13:46 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Jonathan Corbet,
	linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> Describe theory and implementation of hybrid automata in the dedicated
> page hybrid_automata.rst
> Include a section on how to integrate a hybrid automaton in
> monitor_synthesis.rst
> Also remove a hanging $ in deterministic_automata.rst
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
> ---
>  .../trace/rv/deterministic_automata.rst       |   2 +-
>  Documentation/trace/rv/hybrid_automata.rst    | 340 ++++++++++++++++++
>  Documentation/trace/rv/index.rst              |   1 +
>  Documentation/trace/rv/monitor_synthesis.rst  | 107 ++++++
>  4 files changed, 449 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/trace/rv/hybrid_automata.rst
>
> diff --git a/Documentation/trace/rv/deterministic_automata.rst b/Documentation/trace/rv/deterministic_automata.rst
> index d0638f95a455..7a1c2b20ec72 100644
> --- a/Documentation/trace/rv/deterministic_automata.rst
> +++ b/Documentation/trace/rv/deterministic_automata.rst
> @@ -11,7 +11,7 @@ where:
>  - *E* is the finite set of events;
>  - x\ :subscript:`0` is the initial state;
>  - X\ :subscript:`m` (subset of *X*) is the set of marked (or final) states.
> -- *f* : *X* x *E* -> *X* $ is the transition function. It defines the state
> +- *f* : *X* x *E* -> *X* is the transition function. It defines the state
>    transition in the occurrence of an event from *E* in the state *X*. In the
>    special case of deterministic automata, the occurrence of the event in *E*
>    in a state in *X* has a deterministic next state from *X*.
> diff --git a/Documentation/trace/rv/hybrid_automata.rst b/Documentation/trace/rv/hybrid_automata.rst
> new file mode 100644
> index 000000000000..ea701114c54d
> --- /dev/null
> +++ b/Documentation/trace/rv/hybrid_automata.rst
> @@ -0,0 +1,340 @@
> +Hybrid Automata
> +===============
> +
> +Hybrid automata are an extension of deterministic automata, there are several
> +definitions of hybrid automata in the literature. The adaptation implemented
> +here is formally denoted by G and defined as a 7-tuple:
> +
> +        *G* = { *X*, *E*, *V*, *f*, x\ :subscript:`0`, X\ :subscript:`m`, *i* }
> +
> +- *X* is the set of states;
> +- *E* is the finite set of events;
> +- *V* is the finite set of environment variables;
> +- x\ :subscript:`0` is the initial state;
> +- X\ :subscript:`m` (subset of *X*) is the set of marked (or final) states.
> +- *f* : *X* x *E* x *C(V)* -> *X* is the transition function.
> +  It defines the state transition in the occurrence of an event from *E* in the
> +  state *X*. Unlike deterministic automata, the transition function also
> +  includes guards from the set of all possible constraints (defined as *C(V)*).
> +  Guards can be true or false with the valuation of *V* when the event occurs,
> +  and the transition is possible only when constraints are true. Similarly to
> +  deterministic automata, the occurrence of the event in *E* in a state in *X*
> +  has a deterministic next state from *X*, if the guard is true.
> +- *i* : *X* -> *C'(V)* is the invariant assignment function, this is a
> +  constraint assigned to each state in *X*, every state in *X* must be left
> +  before the invariant turns to false. We can omit the representation of
> +  invariants whose value is true regardless of the valuation of *V*.

This brings back bad memories from university..

> +
> +The set of all possible constraints *C(V)* is defined according to the
> +following grammar:
> +
> +        g = v < c | v > c | v <= c | v >= c | v == c | v != c | g && g | true
> +
> +With v a variable in *V* and c a numerical value.
> +
> +We define the special case of hybrid automata whose variables grow with uniform
> +rates as timed automata. In this case, the variables are called clocks.
> +As the name implies, timed automata can be used to describe real time.
> +Additionally, clocks support another type of guard which always evaluates to true:
> +
> +        reset(v)
> +
> +The reset constraint is used to set the value of a clock to 0.
> +
> +The set of invariant constraints *C'(V)* is a subset of *C(V)* including only
> +constraint of the form:
> +
> +        g = v < c | true
> +
> +This simplifies the implementation as a clock expiration is a necessary and
> +sufficient condition for the violation of invariants while still allowing more
> +complex constraints to be specified as guards.
> +
> +It is important to note that any valid hybrid automaton is a valid
> +deterministic automaton with additional guards and invariants. Those can only
> +further constrain what transitions are valid but it is not possible to define
> +transition functions starting from the same state in *X* and the same event in
> +*E* but ending up in different states in *X* based on the valuation of *V*.

Perhaps remove the double "valid". Usually people use the phrase "any
valid A is a valid B" to say that B is a superset of A, but it is
opposite here.

> +
> +Examples
> +--------
> +
> +Wip as hybrid automaton
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The 'wip' (wakeup in preemptive) example introduced as a deterministic automaton
> +can also be described as:
> +
> +- *X* = { ``any_thread_running`` }
> +- *E* = { ``sched_waking`` }
> +- *V* = { ``preemptive`` }
> +- x\ :subscript:`0` = ``any_thread_running``
> +- X\ :subscript:`m` = {``any_thread_running``}
> +- *f* =
> +   - *f*\ (``any_thread_running``, ``sched_waking``, ``preemptive==0``) = ``any_thread_running``
> +- *i* =
> +   - *i*\ (``any_thread_running``) = ``true``
> +
> +Which can be represented graphically as::
> +
> +     |
> +     |
> +     v
> +   #====================#   sched_waking;preemptive==0
> +   H                    H ------------------------------+
> +   H any_thread_running H                               |
> +   H                    H <-----------------------------+
> +   #====================#
> +
> +In this example, by using the preemptive state of the system as an environment
> +variable, we can assert this constraint on ``sched_waking`` without requiring
> +preemption events (as we would in a deterministic automaton), which can be
> +useful in case those events are not available or not reliable on the system.
> +
> +Since all the invariants in *i* are true, we can omit them from the representation.
> +
> +Stall model with guards (iteration 1)
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +As a sample timed automaton we can define 'stall' as:
> +
> +- *X* = { ``dequeued``, ``enqueued``, ``running``}
> +- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
> +- *V* = { ``clk`` }
> +- x\ :subscript:`0` = ``dequeue``
> +- X\ :subscript:`m` = {``dequeue``}
> +- *f* =
> +   - *f*\ (``enqueued``, ``switch_in``, ``clk < threshold``) = ``running``
> +   - *f*\ (``running``, ``dequeue``) = ``dequeued``
> +   - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
> +- *i* = *omitted as all true*
> +
> +Graphically represented as::
> +
> +       |
> +       |
> +       v
> +     #============================#
> +     H          dequeued          H <+
> +     #============================#  |
> +       |                             |
> +       | enqueue; reset(clk)         |
> +       v                             |
> +     +----------------------------+  |
> +     |          enqueued          |  | dequeue
> +     +----------------------------+  |
> +       |                             |
> +       | switch_in; clk < threshold  |
> +       v                             |
> +     +----------------------------+  |
> +     |          running           | -+
> +     +----------------------------+
> +
> +This model imposes that the time between when a task is enqueued (it becomes
> +runnable) and when the task gets to run must be lower than a certain threshold.
> +A failure in this model means that the task is starving.
> +One problem in using guards on the edges in this case is that the model will
> +not report a failure until the ``switch_in`` event occurs. This means that,
> +according to the model, it is valid for the task never to run.
> +
> +Stall model with invariants (iteration 2)
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The first iteration isn't exactly what was intended, we can change the model as:
> +
> +- *X* = { ``dequeued``, ``enqueued``, ``running``}
> +- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
> +- *V* = { ``clk`` }
> +- x\ :subscript:`0` = ``dequeue``
> +- X\ :subscript:`m` = {``dequeue``}
> +- *f* =
> +   - *f*\ (``enqueued``, ``switch_in``) = ``running``
> +   - *f*\ (``running``, ``dequeue``) = ``dequeued``
> +   - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
> +- *i* =
> +   - *i*\ (``enqueued``) = ``clk < threshold``
> +
> +Graphically::
> +
> +    |
> +    |
> +    v
> +  #=========================#
> +  H        dequeued         H <+
> +  #=========================#  |
> +    |                          |
> +    | enqueue; reset(clk)      |
> +    v                          |
> +  +-------------------------+  |
> +  |        enqueued         |  |
> +  |    clk < threshold      |  | dequeue
> +  +-------------------------+  |
> +    |                          |
> +    | switch_in                |
> +    v                          |
> +  +-------------------------+  |
> +  |         running         | -+
> +  +-------------------------+
> +
> +In this case, we moved the guard as an invariant to the ``enqueued`` state,
> +this means we not only forbid the occurrence of ``switch_in`` when ``clk`` is
> +past the threshold but also mark as invalid in case we are *still* in
> +``enqueued`` after the threshold. This model is effectively in an invalid state
> +as soon as a task is starving, rather than when the starving task finally runs.
> +
> +Hybrid Automaton in C
> +---------------------
> +
> +The definition of hybrid automata in C is heavily based on the deterministic
> +automata one. Specifically, we add the set of environment variables and the
> +constraints (both guards on transitions and invariants on states) as follows.
> +This is a combination of both iterations of the stall example::
> +
> +  /* enum representation of X (set of states) to be used as index */
> +  enum states {
> +	dequeued = 0,

I think you already removed this " = 0" in an earlier patch?

> +	enqueued,
> +	running,
> +	state_max
> +  };
> +
> +  #define INVALID_STATE state_max
> +
> +  /* enum representation of E (set of events) to be used as index */
> +  enum events {
> +	dequeue = 0,
> +	enqueue,
> +	switch_in,
> +	event_max
> +  };
> +
> +  /* enum representation of V (set of environment variables) to be used as index */
> +  enum envs {
> +	clk = 0,
> +	env_max,
> +	env_max_stored = env_max
> +  };
> +
> +  struct automaton {
> +	char *state_names[state_max];                  // X: the set of states
> +	char *event_names[event_max];                  // E: the finite set of events
> +	char *env_names[env_max];                      // V: the finite set of env vars
> +	unsigned char function[state_max][event_max];  // f: transition function
> +	unsigned char initial_state;                   // x_0: the initial state
> +	bool final_states[state_max];                  // X_m: the set of marked states
> +  };
> +
> +  struct automaton aut = {
> +	.state_names = {
> +		"dequeued",
> +		"enqueued",
> +		"running"
> +	},
> +	.event_names = {
> +		"dequeue",
> +		"enqueue",
> +		"switch_in"
> +	},
> +	.env_names = {
> +		"clk"
> +	},
> +	.function = {
> +		{ INVALID_STATE,      enqueued, INVALID_STATE },
> +		{ INVALID_STATE, INVALID_STATE,       running },
> +		{      dequeued, INVALID_STATE, INVALID_STATE },
> +	},
> +	.initial_state = dequeued,
> +	.final_states = { 1, 0, 0 },
> +  };
> +
> +  static bool verify_constraint(enum states curr_state, enum events event,
> +                                enum states next_state)
> +  {
> +	bool res = true;
> +
> +	/* Validate guards as part of f */
> +	if (curr_state == enqueued && event == sched_switch_in)
> +		res = get_env(clk) < threshold;
> +	else if (curr_state == dequeued && event == sched_wakeup)
> +		reset_env(clk);
> +
> +	/* Validate invariants in i */
> +    if (next_state == curr_state || !res)
   ^^^^
   indentation error ;)

> +		return res;
> +	if (next_state == enqueued)
> +		ha_start_timer_jiffy(ha_mon, clk, threshold_jiffies);
> +	else if (curr_state == enqueued)
> +		res = !ha_cancel_timer(ha_mon);
> +	return res;
> +  }
> +
> +The function ``verify_constraint``, here reported as simplified, checks guards,
> +performs resets and starts timers to validate invariants according to
> +specification.
> +Due to the complex nature of environment variables, the user needs to provide
> +functions to get and reset environment variables, although we provide some
> +helpers for common types (e.g. clocks with ns or jiffy granularity).

Is there theoretical reason that functions to get/set variables cannot
be generated? Or you just do not have time for it yet?

> +Since invariants are only defined as clock expirations (e.g. *clk <
> +threshold*), the callback for timers armed when entering the state is in fact a
> +failure in the model and triggers a reaction. Leaving the state stops the timer
> +and checks for its expiration, in case the callback was late.

"callback for timers armed when entering the state is in fact a failure
in the model and triggers a reaction." - I have problem parsing this
sentence. How can "callback for timers" be armed? Or do you mean arming
timers while entering a state is a failure in the model? What is it a failure?

> +It is important to note that timers introduce overhead, if the monitor has
> +several instances (e.g. all tasks) this can become an issue.
> +If the monitor is guaranteed to *eventually* leave the state and the incurred
> +delay to wait for the next event is acceptable, guards can be use to lower the
> +monitor's overhead.

How about having some sort of a "background task" which periodically
verifies the invariants?

> +For instance in the stall example, if we are only interested in reporting
> +stalled tasks, rather than reacting as soon as a task is stalled, and
> +``switch_in`` is eventually going to occur, we can use the first iteration.
> +
> +Graphviz .dot format
> +--------------------
> +
> +Also the Graphviz representation of hybrid automata is an extension of the
> +deterministic automata one. Specifically, guards can be provided in the event
> +name separated by ``;``::
> +
> +    "state_start" -> "state_dest" [ label = "sched_waking;preemptible==0;reset(clk)" ];
> +
> +Invariant can be specified in the state label (not the node name!) separated by ``\n``::
> +
> +    "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
> +
> +Constraints can be specified as valid C comparisons and allow spaces, the first
> +element of the comparison must be the clock while the second is a numerical or
> +parametrised value. Guards allow comparisons to be combined with boolean
> +operations (``&&`` and ``||``), resets must be separated from other constraints.
> +
> +This is the full example of the last version of the 'stall' model in DOT::
> +
> +  digraph state_automaton {
> +      {node [shape = circle] "enqueued"};
> +      {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
> +      {node [shape = doublecircle] "dequeued"};
> +      {node [shape = circle] "running"};
> +      "__init_dequeued" -> "dequeued";
> +      "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
> +      "running" [label = "running"];
> +      "dequeued" [label = "dequeued"];
> +      "enqueued" -> "running" [ label = "switch_in" ];
> +      "running" -> "dequeued" [ label = "dequeue" ];
> +      "dequeued" -> "enqueued" [ label = "enqueue;reset(clk)" ];
> +      { rank = min ;
> +          "__init_dequeued";
> +          "dequeued";
> +      }

Btw, the last block (rank = min) doesn't seem to serve any purpose. But
the last time I checked months ago, the parser explodes if it is
removed, not sure if it still does now. But this is another reason that
I would like a rewrite.

Nam

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

* Re: [PATCH v2 14/20] rv: Add sample hybrid monitors stall
  2025-09-19 14:09 ` [PATCH v2 14/20] rv: Add sample hybrid monitors stall Gabriele Monaco
@ 2025-10-10 14:23   ` Nam Cao
  2025-10-13  9:01     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-10 14:23 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Jonathan Corbet, Steven Rostedt,
	Masami Hiramatsu, linux-doc, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> +- Name: stall - wakeup in preemptive
                   ^^^^^^^^^^^^^^^^^^^^
                   copy-paste mistake?

> +- Type: per-task hybrid automaton
> +- Author: Gabriele Monaco <gmonaco@redhat.com>
> +
> +Description
> +-----------
> +
> +The stalled task (stall) monitor is a sample per-task timed monitor that checks
> +if tasks are scheduled within a defined threshold after they are ready::
> +
> +                        |
> +                        |
> +                        v
> +                      #==================================#
> +                      H             dequeued             H <+
> +                      #==================================#  |
> +                        |                                   |
> +                        | sched_wakeup;reset(clk)           |
> +                        v                                   |
> +                      +----------------------------------+  |
> +                      |             enqueued             |  |
> +                      |     clk < threshold_jiffies      |  | sched_switch_wait
> +                      +----------------------------------+  |
> +                        |                                   |
> +                        | sched_switch_in                   |
> +    sched_switch_in     v                                   |
> +    sched_wakeup      +----------------------------------+  |
> +  +------------------ |                                  |  |
> +  |                   |             running              |  |
> +  +-----------------> |                                  | -+
> +                      +----------------------------------+

I think this monitor does not detect if a task get preempted, but then
never get scheduled again?

This sample monitor does not have to cover everything obviously, but I'm
curious if I understand it correct.

Nam

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

* Re: [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton
  2025-09-19 14:09 ` [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
@ 2025-10-10 14:29   ` Nam Cao
  2025-10-13 14:14     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-10 14:29 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Jonathan Corbet,
	Masami Hiramatsu, linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> -                 |                     sched_need_resched
> -                 |                     sched_waking
> -                 |                     irq_entry
> -                 |                   +--------------------+
> -                 v                   v                    |
> -               +------------------------------------------------------+
> -  +----------- |                     disabled                         | <+
> -  |            +------------------------------------------------------+  |
> -  |              |                 ^                                     |
> -  |              |          preempt_disable      sched_need_resched      |
> -  |       preempt_enable           |           +--------------------+    |
> -  |              v                 |           v                    |    |
> -  |            +------------------------------------------------------+  |
> -  |            |                   irq_disabled                       |  |
> -  |            +------------------------------------------------------+  |
> -  |                              |             |        ^                |
> -  |     irq_entry            irq_entry         |        |                |
> -  |     sched_need_resched       v             |   irq_disable           |
> -  |     sched_waking +--------------+          |        |                |
> -  |           +----- |              |     irq_enable    |                |
> -  |           |      |    in_irq    |          |        |                |
> -  |           +----> |              |          |        |                |
> -  |                  +--------------+          |        |          irq_disable
> -  |                     |                      |        |                |
> -  | irq_enable          | irq_enable           |        |                |
> -  |                     v                      v        |                |
> -  |            #======================================================#  |
> -  |            H                     enabled                          H  |
> -  |            #======================================================#  |
> -  |              |                   ^         ^ preempt_enable     |    |
> -  |       preempt_disable     preempt_enable   +--------------------+    |
> -  |              v                   |                                   |
> -  |            +------------------+  |                                   |
> -  +----------> | preempt_disabled | -+                                   |
> -               +------------------+                                      |
> -                 |                                                       |
> -                 +-------------------------------------------------------+
> -
> +
> +   |
> +   |
> +   v
> + #=========#   sched_need_resched;irq_off == 1
> + H         H   sched_waking;irq_off == 1 && preempt_off == 1
> + H   any   H ------------------------------------------------+
> + H         H                                                 |
> + H         H <-----------------------------------------------+
> + #=========#

Nice!

> +		 * If CONFIG_PREEMPTION is enabled, then the tracepoint itself disables
> +		 * preemption (adding one to the preempt_count). Since we are
> +		 * interested in the preempt_count at the time the tracepoint was
> +		 * hit, we consider 1 as still enabled.
> +		 */
> +		if (IS_ENABLED(CONFIG_PREEMPTION))
> +			return (preempt_count() & PREEMPT_MASK) > 1;

FYI, there is plan to keep preemption enabled during tracepoint
handling. So keep that in mind when this monitor breaks.

Nam

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

* Re: [PATCH v2 20/20] rv: Add deadline monitors
  2025-09-19 14:09 ` [PATCH v2 20/20] rv: Add deadline monitors Gabriele Monaco
@ 2025-10-10 15:04   ` Nam Cao
  2025-10-13  7:30     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-10 15:04 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Jonathan Corbet,
	Masami Hiramatsu, linux-trace-kernel, linux-doc
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> +/*
> + * Dummy values if not available
> + */
> +#ifndef __NR_sched_setscheduler
> +#define __NR_sched_setscheduler -1
> +#endif
> +#ifndef __NR_sched_setattr
> +#define __NR_sched_setattr -2
> +#endif

You can use __COUNTER__ intead, would make it easier to add to or remove
from this list later on.

> +static void handle_syscall(void *data, struct pt_regs *regs, long id)
> +{
> +	struct task_struct *p;
> +	int new_policy = -1;
> +
> +	new_policy = extract_params(regs, id, &p);
> +	if (new_policy < 0 || new_policy == p->policy)
> +		return;
> +	if (p->policy == SCHED_DEADLINE)
> +		da_reset(EXPAND_ID(&p->dl, DL_TASK));
> +	else if (new_policy == SCHED_DEADLINE)
> +		da_create_conditional(EXPAND_ID(&p->dl, DL_TASK), GFP_NOWAIT);
> +}

What if the syscall fails for any reason? Wouldn't the monitor stores
incorrect information?

Nam

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

* Re: [PATCH v2 20/20] rv: Add deadline monitors
  2025-10-10 15:04   ` Nam Cao
@ 2025-10-13  7:30     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-13  7:30 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, Jonathan Corbet,
	Masami Hiramatsu, linux-trace-kernel, linux-doc
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-10 at 17:04 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > +/*
> > + * Dummy values if not available
> > + */
> > +#ifndef __NR_sched_setscheduler
> > +#define __NR_sched_setscheduler -1
> > +#endif
> > +#ifndef __NR_sched_setattr
> > +#define __NR_sched_setattr -2
> > +#endif
> 
> You can use __COUNTER__ intead, would make it easier to add to or remove
> from this list later on.

Right, good point.

> > +static void handle_syscall(void *data, struct pt_regs *regs, long id)
> > +{
> > +	struct task_struct *p;
> > +	int new_policy = -1;
> > +
> > +	new_policy = extract_params(regs, id, &p);
> > +	if (new_policy < 0 || new_policy == p->policy)
> > +		return;
> > +	if (p->policy == SCHED_DEADLINE)
> > +		da_reset(EXPAND_ID(&p->dl, DL_TASK));
> > +	else if (new_policy == SCHED_DEADLINE)
> > +		da_create_conditional(EXPAND_ID(&p->dl, DL_TASK),
> > GFP_NOWAIT);
> > +}
> 
> What if the syscall fails for any reason? Wouldn't the monitor stores
> incorrect information?

Yes, that could happen, this seems an issue only in the throttle monitor when
failing a DEADLINE->NORMAL, other cases are at most resetting the monitor.

In this case, the monitor is really saying: a deadline task is no longer
deadline, so its runtime should be counted as fair server. If the syscall does
fail, we are assuming the fair server as armed (not running) for a bit more.

Here I couldn't find a nice way to handle this: if I attach only to the syscall
return, I see if it succeeded but I lost the previous policy, attaching to both
(and keep a per-cpu storage just for this process) seems a bit overcomplicated.
I see this can potentially lead to errors in the monitor, but I'm not sure the
extra complexity is worth it here, considering this failure might only happen if
a user really makes wrong calls.
I tried to avoid creating a new tracepoint on a successful policy change.

What do you think?

Thanks,
Gabriele


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

* Re: [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata
  2025-10-10 13:46   ` Nam Cao
@ 2025-10-13  8:33     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-13  8:33 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, Jonathan Corbet,
	linux-trace-kernel, linux-doc
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-10 at 15:46 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > Describe theory and implementation of hybrid automata in the dedicated
> > page hybrid_automata.rst
> > Include a section on how to integrate a hybrid automaton in
> > monitor_synthesis.rst
> > Also remove a hanging $ in deterministic_automata.rst
> > 
> > Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
> > ---
> This brings back bad memories from university..

:')

> > +It is important to note that any valid hybrid automaton is a valid
> > +deterministic automaton
> 
> Perhaps remove the double "valid". Usually people use the phrase "any
> valid A is a valid B" to say that B is a superset of A, but it is
> opposite here.

Alright, will do.

> > +This is a combination of both iterations of the stall example::
> > +
> > +  /* enum representation of X (set of states) to be used as index */
> > +  enum states {
> > +	dequeued = 0,
> 
> I think you already removed this " = 0" in an earlier patch?

Right, missed that.

> > +	/* Validate invariants in i */
> > +    if (next_state == curr_state || !res)
>    ^^^^
>    indentation error ;)

Good catch.

> > +Due to the complex nature of environment variables, the user needs to
> > provide
> > +functions to get and reset environment variables, although we provide some
> > +helpers for common types (e.g. clocks with ns or jiffy granularity).
> 
> Is there theoretical reason that functions to get/set variables cannot
> be generated? Or you just do not have time for it yet?

Not theoretical but practical, the monitor cannot always define /what/ an
environment variable is. In case of clocks (jiffy and ns) that's easy and the
parser does in fact generate get and reset functions, the user only needs to
specify the measure unit as explained somewhere else.

It is possible to add more exotic variables that don't follow common clock rules
and need different get/reset definitions. Now, in practice, that may not happen
with clocks (I cannot think of an alternative clock definition), but can happen
for other variables. For instance if the variable describes the preempt count,
the model cannot know in advance and the user will need to supply how to read
that in the kernel (just like we do with tracepoints, although event names
/might/ hint something).

As I get it, this isn't so clear so I should probably try and reword it.

I might just assume variables without unit but with a reset are, say, jiffy
clocks and never expect manual definition of the reset function, but that might
be misleading at times: e.g. if a user wants a ns clock but forgets the unit,
the monitor would still build.

> 
> > +Since invariants are only defined as clock expirations (e.g. *clk <
> > +threshold*), the callback for timers armed when entering the state is in
> > fact a
> > +failure in the model and triggers a reaction. Leaving the state stops the
> > timer
> > +and checks for its expiration, in case the callback was late.
> 
> "callback for timers armed when entering the state is in fact a failure
> in the model and triggers a reaction." - I have problem parsing this
> sentence. How can "callback for timers" be armed? Or do you mean arming
> timers while entering a state is a failure in the model? What is it a failure?

Right, that sentence doesn't make sense.
We arm a timer when entering the state, expiration of such timer is a failure.
The timer is cancelled when leaving the state, so in fact leaving the state
before the timer expiration is the only valid behaviour.

> > +It is important to note that timers introduce overhead, if the monitor has
> > +several instances (e.g. all tasks) this can become an issue.
> > +If the monitor is guaranteed to *eventually* leave the state and the
> > incurred
> > +delay to wait for the next event is acceptable, guards can be use to lower
> > the
> > +monitor's overhead.
> 
> How about having some sort of a "background task" which periodically
> verifies the invariants?

I didn't update this part, but now timers can work also via timer wheel, which
is cutting down costs by sacrificing some reactivity (not correctness though). I
assume the background thread would be quite similar to what the timer wheel
already does.

But I definitely need to mention this because the timer wheel is not as heavy as
the hrtimers and its overhead is usually acceptable (unless proven otherwise for
a specific monitor/workload, I'd say).

> > +This is the full example of the last version of the 'stall' model in DOT::
> > +
> > +  digraph state_automaton {
> > +      {node [shape = circle] "enqueued"};
> > +      {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
> > +      {node [shape = doublecircle] "dequeued"};
> > +      {node [shape = circle] "running"};
> > +      "__init_dequeued" -> "dequeued";
> > +      "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
> > +      "running" [label = "running"];
> > +      "dequeued" [label = "dequeued"];
> > +      "enqueued" -> "running" [ label = "switch_in" ];
> > +      "running" -> "dequeued" [ label = "dequeue" ];
> > +      "dequeued" -> "enqueued" [ label = "enqueue;reset(clk)" ];
> > +      { rank = min ;
> > +          "__init_dequeued";
> > +          "dequeued";
> > +      }
> 
> Btw, the last block (rank = min) doesn't seem to serve any purpose. But
> the last time I checked months ago, the parser explodes if it is
> removed, not sure if it still does now. But this is another reason that
> I would like a rewrite.

Mmh, that's automatically generated by Supremica and, I believe, in some models
it's tuning a bit the position of nodes. Quite strange that the parser exploded,
those lines should be completely ignored.. Still, we know the parser needs this
big refactor.

Thanks,
Gabriele


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

* Re: [PATCH v2 14/20] rv: Add sample hybrid monitors stall
  2025-10-10 14:23   ` Nam Cao
@ 2025-10-13  9:01     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-13  9:01 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Jonathan Corbet, Steven Rostedt,
	Masami Hiramatsu, linux-doc, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-10 at 16:23 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > +- Name: stall - wakeup in preemptive
>                    ^^^^^^^^^^^^^^^^^^^^
>                    copy-paste mistake?

Damn, right..

> > +- Type: per-task hybrid automaton
> > +- Author: Gabriele Monaco <gmonaco@redhat.com>
> > +
> > +Description
> > +-----------
> > +
> > +The stalled task (stall) monitor is a sample per-task timed monitor that
> > checks
> > +if tasks are scheduled within a defined threshold after they are ready::
> > +
> > +                        |
> > +                        |
> > +                        v
> > +                      #==================================#
> > +                      H             dequeued             H <+
> > +                      #==================================#  |
> > +                        |                                   |
> > +                        | sched_wakeup;reset(clk)           |
> > +                        v                                   |
> > +                      +----------------------------------+  |
> > +                      |             enqueued             |  |
> > +                      |     clk < threshold_jiffies      |  |
> > sched_switch_wait
> > +                      +----------------------------------+  |
> > +                        |                                   |
> > +                        | sched_switch_in                   |
> > +    sched_switch_in     v                                   |
> > +    sched_wakeup      +----------------------------------+  |
> > +  +------------------ |                                  |  |
> > +  |                   |             running              |  |
> > +  +-----------------> |                                  | -+
> > +                      +----------------------------------+
> 
> I think this monitor does not detect if a task get preempted, but then
> never get scheduled again?
> 
> This sample monitor does not have to cover everything obviously, but I'm
> curious if I understand it correct.

Yes, that's right. I think I could cover that scenario by just going back to
enqueued and resetting when preempted. Since it's a simple change, it's probably
something meaningful to do..

Thanks,
Gabriele

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

* Re: [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton
  2025-10-10 14:29   ` Nam Cao
@ 2025-10-13 14:14     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-13 14:14 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, Jonathan Corbet,
	Masami Hiramatsu, linux-trace-kernel, linux-doc
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-10 at 16:29 +0200, Nam Cao wrote:
> > +		 * If CONFIG_PREEMPTION is enabled, then the tracepoint
> > itself disables
> > +		 * preemption (adding one to the preempt_count). Since we
> > are
> > +		 * interested in the preempt_count at the time the
> > tracepoint was
> > +		 * hit, we consider 1 as still enabled.
> > +		 */
> > +		if (IS_ENABLED(CONFIG_PREEMPTION))
> > +			return (preempt_count() & PREEMPT_MASK) > 1;
> 
> FYI, there is plan to keep preemption enabled during tracepoint
> handling. So keep that in mind when this monitor breaks.

Right, I've heard of it, thanks for the heads up. I'll adapt the monitors once
that happens.

Thanks,
Gabriele


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

* Re: [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-09-19 14:09 ` [PATCH v2 10/20] rv: Add Hybrid Automata monitor type Gabriele Monaco
@ 2025-10-17  8:44   ` Nam Cao
  2025-10-17  9:48     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-17  8:44 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
> new file mode 100644
> index 000000000000..fb885709b727
> --- /dev/null
> +++ b/include/rv/ha_monitor.h
> @@ -0,0 +1,469 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2028 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
> + *
> + * Hybrid automata (HA) monitor functions, to be used together
> + * with automata models in C generated by the dot2k tool.
> + *
> + * This type of monitors extends the Deterministic automata (DA) class by
> + * adding a set of environment variables (e.g. clocks) that can be used to
> + * constraint the valid transitions.
> + *
> + * The dot2k tool is available at tools/verification/dot2k/
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^
                                     non-existent
> +/*
> + * ktime_get_ns is expensive, since we usually don't require precise accounting
> + * of changes within the same event, cache the current time at the beginning of
> + * the constraint handler and use the cache for subsequent calls.
> + * Monitors without ns clocks automatically skip this.
> + */
> +#ifdef HA_CLK_NS
> +#define ha_update_ns_cache() ktime_get_ns()
> +#else
> +#define ha_update_ns_cache() 0
> +#endif /* HA_CLK_NS */

ha_update_ns_cache() itself does not cache, its caller does. So I think
it is misleading to name this "cache".

Why is "update" in the name? Isn't something like ha_get_ns() betters
describe this macro?

> +/*
> + * 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

Why does guard representation store the last reset?

For example, what if I specifiy the guard as "clk > 500ns". Shouldn't
the guard representation be "500ns"?

> +static inline u64 ha_get_passed_ns(struct ha_monitor *ha_mon, enum envs env,
> +				   u64 expire, 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);

The function is named *get*() which indicates that it only reads. But it
also writes.

> +/*
> + * Retrieve the last reset time (guard representation) from the invariant
> + * representation (expiration).
> + * It the caller's responsibility to make sure the storage was actually in the
> + * invariant representation (e.g. the current state has an invariant).
> + * The provided value must be the same used when starting the invariant.
> + *
> + * This function's access to the storage is NOT atomic, due to the rarity when
> + * this is used. If a monitor allows writes concurrent to this, likely
> + * other things are broken and need rethinking the model or additional locking.

Does atomic_sub() solves your "NOT atomic" problem?

> +static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
> +					u64 expire, u64 time_ns)
> +{
> +	u64 passed = ha_get_passed_jiffy(ha_mon, env, expire, time_ns);
> +
> +	mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
> +}

I don't understand this "passed" thing. I see this function being called
in ha_setup_invariants() of stall monitor. Stall monitor is validating
that the task does not stay in "enqueued" state for more than
"threshold_jiffies". If so, what is the purpose of "passed"? From my
understanding, it should be just 

static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
					u64 expire, u64 time_ns)
{
	mod_timer(&ha_mon->timer, get_jiffies_64() + expire);
}

what do I miss?

Nam

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

* Re: [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata
  2025-09-19 14:09 ` [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
@ 2025-10-17  9:37   ` Nam Cao
  2025-10-17  9:53     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-17  9:37 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> +            if any(u in self.env_types.values() for u in ["ns", "us", "ms", "s"]):
> +                buff.append("#define HA_CLK_NS")

That any() twisted my brain. Does the following work?

    if not {"ns", "us", "ms", "s"}.isdisjoint(self.env_types.values())

> +        match unit:
> +            case "us":
> +                value *= 1000
> +            case "ms":
> +                value *= 1000000
> +            case "s":
> +                value *= 1000000000

Use 10**9 instead, so that we don't have to count the zeroes.

Nam

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

* Re: [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-10-17  8:44   ` Nam Cao
@ 2025-10-17  9:48     ` Gabriele Monaco
  2025-10-17 13:05       ` Nam Cao
  0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-17  9:48 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-17 at 10:44 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
> > new file mode 100644
> > index 000000000000..fb885709b727
> > --- /dev/null
> > +++ b/include/rv/ha_monitor.h
> > @@ -0,0 +1,469 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2025-2028 Red Hat, Inc. Gabriele Monaco
> > <gmonaco@redhat.com>
> > + *
> > + * Hybrid automata (HA) monitor functions, to be used together
> > + * with automata models in C generated by the dot2k tool.
> > + *
> > + * This type of monitors extends the Deterministic automata (DA) class by
> > + * adding a set of environment variables (e.g. clocks) that can be used to
> > + * constraint the valid transitions.
> > + *
> > + * The dot2k tool is available at tools/verification/dot2k/
>                                      ^^^^^^^^^^^^^^^^^^^^^^^^^
>                                      non-existent

Right, need to update also in DA.

> > +/*
> > + * ktime_get_ns is expensive, since we usually don't require precise
> > accounting
> > + * of changes within the same event, cache the current time at the
> > beginning of
> > + * the constraint handler and use the cache for subsequent calls.
> > + * Monitors without ns clocks automatically skip this.
> > + */
> > +#ifdef HA_CLK_NS
> > +#define ha_update_ns_cache() ktime_get_ns()
> > +#else
> > +#define ha_update_ns_cache() 0
> > +#endif /* HA_CLK_NS */
> 
> ha_update_ns_cache() itself does not cache, its caller does. So I think
> it is misleading to name this "cache".
> 
> Why is "update" in the name? Isn't something like ha_get_ns() betters
> describe this macro?
> 

Good point.

> > +/*
> > + * 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
> 
> Why does guard representation store the last reset?
> 
> For example, what if I specifiy the guard as "clk > 500ns". Shouldn't
> the guard representation be "500ns"?

Alright, this is the simplest way I could think to represent clocks, still it
seems confusing.

Let's start from guards (invariants are not special but I'm trying to do
something to keep precision), the value of a clock is the time that passed since
the last reset, as that's when the value is set to 0. Storing that timestamp and
just comparing the difference whenever you need to know the valuation of said
clock seemed the most straightforward thing to me. The clock representation
doesn't include the guard constraint, that is validated during the event using
the current valuation (i.e. now - reset_time).

What is important to note is that, at time of reset, you don't know what guard
is going to fire, you may as well have a state with event A asking for clk<10
and event B requiring clk<20, also the guard may be in a later state and may
depend on the path.

Invariants are bound to the form clk < N, and get "armed" when we reach the
state, from there we know exactly when the invariant is going to expire, so we
can save that (very important when using the timer wheel). Note here that the
expiration isn't exactly N from now, but it's the valuation of the clock (reset
might have occurred a few states earlier, see the nomiss case) subtracted by N,
this is what the "passed" means later.

That said, I couldn't think of a simpler implementation but any suggestion is
welcome, of course.

> > +static inline u64 ha_get_passed_ns(struct ha_monitor *ha_mon, enum envs
> > env,
> > +				   u64 expire, 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);
> 
> The function is named *get*() which indicates that it only reads. But it
> also writes.

Right.

> 
> > +/*
> > + * Retrieve the last reset time (guard representation) from the invariant
> > + * representation (expiration).
> > + * It the caller's responsibility to make sure the storage was actually in
> > the
> > + * invariant representation (e.g. the current state has an invariant).
> > + * The provided value must be the same used when starting the invariant.
> > + *
> > + * This function's access to the storage is NOT atomic, due to the rarity
> > when
> > + * this is used. If a monitor allows writes concurrent to this, likely
> > + * other things are broken and need rethinking the model or additional
> > locking.
> 
> Does atomic_sub() solves your "NOT atomic" problem?

Kinda, it would solve the problem for this specific subtraction, but racing
handlers could still lead to problems although the subtraction is "correct".

Since this is the only time the env storage needs to be an atomic_t and it's
fairly rare (only complicated models require calling this function at all,
others are happy with READ_ONCE/WRITE_ONCE) I didn't want to change the storage
implementation for some perceived safety.

I wrote that comment exactly to motivate why we aren't using atomic_t, but I
should probably reword that. Does this make sense to you?

> 
> > +static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum
> > envs env,
> > +					u64 expire, u64 time_ns)
> > +{
> > +	u64 passed = ha_get_passed_jiffy(ha_mon, env, expire, time_ns);
> > +
> > +	mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
> > +}
> 
> I don't understand this "passed" thing. I see this function being called
> in ha_setup_invariants() of stall monitor. Stall monitor is validating
> that the task does not stay in "enqueued" state for more than
> "threshold_jiffies". If so, what is the purpose of "passed"? From my
> understanding, it should be just 
> 
> static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs
> env,
> 					u64 expire, u64 time_ns)
> {
> 	mod_timer(&ha_mon->timer, get_jiffies_64() + expire);
> }
> 
> what do I miss?

As mentioned before, this is true for the stall case, where the reset occurred
when reaching the state with the invariant (passed is close to 0), if you look
at the nomiss case, reset happens before being ready (its invariant would have
passed close to 0), but the same invariant is enforced in running, here we will
see a passed far from 0 and need to take that into account when setting the
invariant.

This is by the way what made necessary caching the ns time, otherwise we'd end
up reading the ktime multiple times in the same handler for fairly no reason.

I've been stuck on this implementation for a while so I definitely need an
outsider perspective to see if things make sense!

Thanks,
Gabriele


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

* Re: [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata
  2025-10-17  9:37   ` Nam Cao
@ 2025-10-17  9:53     ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-17  9:53 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-17 at 11:37 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > +            if any(u in self.env_types.values() for u in ["ns", "us", "ms",
> > "s"]):
> > +                buff.append("#define HA_CLK_NS")
>
> That any() twisted my brain. Does the following work?
>
>     if not {"ns", "us", "ms", "s"}.isdisjoint(self.env_types.values())
>

Yeah that should do, thanks for the suggestion!

> > +        match unit:
> > +            case "us":
> > +                value *= 1000
> > +            case "ms":
> > +                value *= 1000000
> > +            case "s":
> > +                value *= 1000000000
>
> Use 10**9 instead, so that we don't have to count the zeroes.

Right, that's not C.. Will fix.

Thanks,
Gabriele


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

* Re: [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-10-17  9:48     ` Gabriele Monaco
@ 2025-10-17 13:05       ` Nam Cao
  2025-10-17 15:22         ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-17 13:05 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> Alright, this is the simplest way I could think to represent clocks, still it
> seems confusing.
>
> Let's start from guards (invariants are not special but I'm trying to do
> something to keep precision), the value of a clock is the time that passed since
> the last reset, as that's when the value is set to 0. Storing that timestamp and
> just comparing the difference whenever you need to know the valuation of said
> clock seemed the most straightforward thing to me. The clock representation
> doesn't include the guard constraint, that is validated during the event using
> the current valuation (i.e. now - reset_time).
>
> What is important to note is that, at time of reset, you don't know what guard
> is going to fire, you may as well have a state with event A asking for clk<10
> and event B requiring clk<20, also the guard may be in a later state and may
> depend on the path.
>
> Invariants are bound to the form clk < N, and get "armed" when we reach the
> state, from there we know exactly when the invariant is going to expire, so we
> can save that (very important when using the timer wheel). Note here that the
> expiration isn't exactly N from now, but it's the valuation of the clock (reset
> might have occurred a few states earlier, see the nomiss case) subtracted by N,
> this is what the "passed" means later.
>
> That said, I couldn't think of a simpler implementation but any suggestion is
> welcome, of course.

Ok, now things start to make sense. Thanks for the explanation.

At least to me, using the same variable to store different time values
is a bit confusing.

Is it possible that we always store the timestamp of the last clock reset?

The invariant bound value (N) is fixed for each state, so we can have
the bound value in ha_verify_invariants() instead. For example, the
Python script can generate something like

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 == enqueued_stall)
               return ha_check_invariant_jiffy(ha_mon, threshold_jiffies, time_ns);
       return true;
}

Is that possible?

> Kinda, it would solve the problem for this specific subtraction, but racing
> handlers could still lead to problems although the subtraction is "correct".
>
> Since this is the only time the env storage needs to be an atomic_t and it's
> fairly rare (only complicated models require calling this function at all,
> others are happy with READ_ONCE/WRITE_ONCE) I didn't want to change the storage
> implementation for some perceived safety.
>
> I wrote that comment exactly to motivate why we aren't using atomic_t, but I
> should probably reword that. Does this make sense to you?

I think if we always store the timestamp since last reset, we can get
rid of this function. Let's see how that discussion go..

> As mentioned before, this is true for the stall case, where the reset occurred
> when reaching the state with the invariant (passed is close to 0), if you look
> at the nomiss case, reset happens before being ready (its invariant would have
> passed close to 0), but the same invariant is enforced in running, here we will
> see a passed far from 0 and need to take that into account when setting the
> invariant.

Make sense, thanks!

Nam

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

* Re: [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-10-17 13:05       ` Nam Cao
@ 2025-10-17 15:22         ` Gabriele Monaco
  2025-10-20 13:43           ` Nam Cao
  0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-17 15:22 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Fri, 2025-10-17 at 15:05 +0200, Nam Cao wrote:
> 
> Ok, now things start to make sense. Thanks for the explanation.
> 
> At least to me, using the same variable to store different time values
> is a bit confusing.
> 
> Is it possible that we always store the timestamp of the last clock reset?
> 
> The invariant bound value (N) is fixed for each state, so we can have
> the bound value in ha_verify_invariants() instead. For example, the
> Python script can generate something like
> 
> 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 == enqueued_stall)
>                return ha_check_invariant_jiffy(ha_mon, threshold_jiffies,
> time_ns);
>        return true;
> }
> 
> Is that possible?

Alright, that /should/ be possible, provided the value used to set invariants is
constant or at least doesn't change until we leave the state.
This seems a fair assumption to make but doesn't stand for the throttle monitor,
in that case I read the remaining runtime from the dl entity, that one is
updated frequently, for instance when a task is throttled, it's negative, but
this doesn't mean the invariant should expect time to be negative.

Runtime is consumed only when a task is running, so here I use an invariant set
up on the /remaining/ runtime when reaching the running state, that's why also
switch_in resets the clock (runtime is not replenished, but the runtime_left
value doesn't need to be subtracted anything).

An alternative would be to have some sort of pause/resume operations on clocks,
and a task would just pause the clock when preempted, but those operations are
not backed up by theory and wouldn't really simplify the implementation (use 2
variables per clock or a single one and some hack to mark it as paused).

Again, there may be better ways, but I found this one the "simplest".

Does it makes sense or am I just crystallising to this implementation?

Thanks,
Gabriele

> 
> > Kinda, it would solve the problem for this specific subtraction, but racing
> > handlers could still lead to problems although the subtraction is "correct".
> > 
> > Since this is the only time the env storage needs to be an atomic_t and it's
> > fairly rare (only complicated models require calling this function at all,
> > others are happy with READ_ONCE/WRITE_ONCE) I didn't want to change the
> > storage
> > implementation for some perceived safety.
> > 
> > I wrote that comment exactly to motivate why we aren't using atomic_t, but I
> > should probably reword that. Does this make sense to you?
> 
> I think if we always store the timestamp since last reset, we can get
> rid of this function. Let's see how that discussion go..
> > 


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

* Re: [PATCH v2 10/20] rv: Add Hybrid Automata monitor type
  2025-10-17 15:22         ` Gabriele Monaco
@ 2025-10-20 13:43           ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-20 13:43 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
	linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> Alright, that /should/ be possible, provided the value used to set invariants is
> constant or at least doesn't change until we leave the state.
> This seems a fair assumption to make but doesn't stand for the throttle monitor,
> in that case I read the remaining runtime from the dl entity, that one is
> updated frequently, for instance when a task is throttled, it's negative, but
> this doesn't mean the invariant should expect time to be negative.
>
> Runtime is consumed only when a task is running, so here I use an invariant set
> up on the /remaining/ runtime when reaching the running state, that's why also
> switch_in resets the clock (runtime is not replenished, but the runtime_left
> value doesn't need to be subtracted anything).
>
> An alternative would be to have some sort of pause/resume operations on clocks,
> and a task would just pause the clock when preempted, but those operations are
> not backed up by theory and wouldn't really simplify the implementation (use 2
> variables per clock or a single one and some hack to mark it as paused).
>
> Again, there may be better ways, but I found this one the "simplest".
>
> Does it makes sense or am I just crystallising to this implementation?

Ok, now it makes sense.

I have been thinking about this in the past days, and didn't come up
with anything better. Let's leave it be.

Nam

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

* Re: [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA
  2025-09-19 14:09 ` [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
@ 2025-10-21 11:55   ` Nam Cao
  2025-10-21 15:54     ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-21 11:55 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> +static inline struct da_monitor *da_create_storage(da_id_type id,
> +						   monitor_target target,
> +						   struct da_monitor *da_mon,
> +						   gfp_t flags)
> +{
> +	struct da_monitor_storage *mon_storage;
> +
> +	if (da_mon)
> +		return da_mon;

I think this 'if' should be moved to da_create_conditional() instead,
because the "conditional" part should be implemented in the function
whose name includes "conditional". I think that would make the code
easier to follow, because one would already have a good guess what the
function does without looking into the details.
> +static inline bool da_handle_start_event(da_id_type id, monitor_target target,
> +					 enum events event)
> +{
> +	struct da_monitor *da_mon;
> +
> +	if (!da_monitor_enabled())
> +		return 0;
> +	guard(rcu)();
> +	da_mon = da_get_monitor(id, target);
> +	da_mon = da_monitor_start_hook(id, target, da_mon);

Do you plan this da_monitor_start_hook() macro to do anything other than
storage preparation? If not, perhaps it is better to name it
da_monitor_prepare_storage() or something like that, so that this is
easier to follow.

Nam

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

* Re: [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors
  2025-09-19 14:09 ` [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
@ 2025-10-21 12:00   ` Nam Cao
  0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-10-21 12:00 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> The special per-object monitor type was just introduced in RV, this
> requires the user to define some functions and type specific to the
> object.
>
> Adapt rvgen to add stub definitions for the monitor_target type, the
> da_get_id() function and other modifications required to create
> per-object monitors.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>

Reviewed-by: Nam Cao <namcao@linutronix.de>

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

* Re: [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors
  2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
                   ` (19 preceding siblings ...)
  2025-09-19 14:09 ` [PATCH v2 20/20] rv: Add deadline monitors Gabriele Monaco
@ 2025-10-21 12:05 ` Nam Cao
  2025-10-21 15:45   ` Gabriele Monaco
  20 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-21 12:05 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt
  Cc: Gabriele Monaco, Tomas Glozar, Juri Lelli, Clark Williams,
	John Kacur, linux-trace-kernel

Gabriele Monaco <gmonaco@redhat.com> writes:
> This RFC contains several related changes, I'm sending them together to
> give the full picture and get early feedback but I'm probably going to
> submit some of them separately.

I'm done reviewing the series, sorry for taking forever (I have other
urgent stuffs going on, and this series is not easy to digest).

Overall looks decent. Thank you for this work!

Nam

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

* Re: [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors
  2025-10-21 12:05 ` [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and " Nam Cao
@ 2025-10-21 15:45   ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-21 15:45 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur,
	linux-trace-kernel

On Tue, 2025-10-21 at 14:05 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > This RFC contains several related changes, I'm sending them together to
> > give the full picture and get early feedback but I'm probably going to
> > submit some of them separately.
> 
> I'm done reviewing the series, sorry for taking forever (I have other
> urgent stuffs going on, and this series is not easy to digest).
> 
> Overall looks decent. Thank you for this work!

Thanks, that was really appreciated! This is quite a fat series to review, I am
well aware of that!

Gabriele


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

* Re: [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA
  2025-10-21 11:55   ` Nam Cao
@ 2025-10-21 15:54     ` Gabriele Monaco
  2025-10-23 12:36       ` Nam Cao
  0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-21 15:54 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Tue, 2025-10-21 at 13:55 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > +static inline struct da_monitor *da_create_storage(da_id_type id,
> > +						   monitor_target target,
> > +						   struct da_monitor
> > *da_mon,
> > +						   gfp_t flags)
> > +{
> > +	struct da_monitor_storage *mon_storage;
> > +
> > +	if (da_mon)
> > +		return da_mon;
> 
> I think this 'if' should be moved to da_create_conditional() instead,
> because the "conditional" part should be implemented in the function
> whose name includes "conditional". I think that would make the code
> easier to follow, because one would already have a good guess what the
> function does without looking into the details.

I get this is confusing and poorly documented, also the _conditional function is
not more conditional than the other, so I'd really need to rethink the name (if
it really needs to stay).

The main purpose of the functions under da_monitor_start_hook is to return a
(possibly new) storage, it's perfectly possible, even likely, that the da_mon is
not null, in such case we don't do any further allocation.

Ignoring the vagueness of the name, the two implementations of this hook are to
call an allocation (yes, always conditionally) or just assign the target to a
pre-allocated storage.
Your suggestion of da_monitor_prepare_storage might fit both descriptions.

In fact, having something like kmalloc_nolock (if it works as I expect it would)
might save this gymnastics as we may not even care about pre-allocating, I'm
going to try it out and perhaps re-think this entire mess.

> > +static inline bool da_handle_start_event(da_id_type id, monitor_target
> > target,
> > +					 enum events event)
> > +{
> > +	struct da_monitor *da_mon;
> > +
> > +	if (!da_monitor_enabled())
> > +		return 0;
> > +	guard(rcu)();
> > +	da_mon = da_get_monitor(id, target);
> > +	da_mon = da_monitor_start_hook(id, target, da_mon);
> 
> Do you plan this da_monitor_start_hook() macro to do anything other than
> storage preparation? If not, perhaps it is better to name it
> da_monitor_prepare_storage() or something like that, so that this is
> easier to follow.

That's a good point, this appears already only in per-object code, the general
name is really just pointless.

Thanks,
Gabriele


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

* Re: [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA
  2025-10-21 15:54     ` Gabriele Monaco
@ 2025-10-23 12:36       ` Nam Cao
  2025-10-24  9:20         ` Gabriele Monaco
  0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-10-23 12:36 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

Gabriele Monaco <gmonaco@redhat.com> writes:
> Ignoring the vagueness of the name, the two implementations of this hook are to
> call an allocation (yes, always conditionally) or just assign the target to a
> pre-allocated storage.
> Your suggestion of da_monitor_prepare_storage might fit both descriptions.

Now that I have grasped (or at least I think so) the patches, it is not
that important to me anymore. But still, having function names which
precisely describe what they do would help new people understand the code.

Nam

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

* Re: [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA
  2025-10-23 12:36       ` Nam Cao
@ 2025-10-24  9:20         ` Gabriele Monaco
  0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-10-24  9:20 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, Steven Rostedt, linux-trace-kernel
  Cc: Tomas Glozar, Juri Lelli, Clark Williams, John Kacur

On Thu, 2025-10-23 at 14:36 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > Ignoring the vagueness of the name, the two implementations of this hook are
> > to call an allocation (yes, always conditionally) or just assign the target
> > to a pre-allocated storage.
> > Your suggestion of da_monitor_prepare_storage might fit both descriptions.
> 
> Now that I have grasped (or at least I think so) the patches, it is not
> that important to me anymore. But still, having function names which
> precisely describe what they do would help new people understand the code.

Agree, I'm going to use more meaningful function names. Apparently the
kmalloc_nolock is still getting lockdep complain on RT kernels so I'm going to
keep this skip-preallocation logic as it is, hopefully just clearer.

Thanks,
Gabriele


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

end of thread, other threads:[~2025-10-24  9:20 UTC | newest]

Thread overview: 57+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-19 14:09 [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 01/20] rv: Refactor da_monitor to minimise macros Gabriele Monaco
2025-10-02  8:45   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 02/20] rv: Cleanup da_monitor after refactor Gabriele Monaco
2025-10-02  8:49   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 03/20] rv: Unify DA event handling functions across monitor types Gabriele Monaco
2025-10-02  9:14   ` Nam Cao
2025-10-02 11:30     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 04/20] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
2025-10-02  9:26   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 05/20] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
2025-10-02  9:34   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 06/20] verification/rvgen: Annotate DA functions with types Gabriele Monaco
2025-10-02  9:39   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 07/20] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 08/20] verification/dot2c: Remove superfluous enum assignment and add last comma Gabriele Monaco
2025-10-02  9:40   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 09/20] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 10/20] rv: Add Hybrid Automata monitor type Gabriele Monaco
2025-10-17  8:44   ` Nam Cao
2025-10-17  9:48     ` Gabriele Monaco
2025-10-17 13:05       ` Nam Cao
2025-10-17 15:22         ` Gabriele Monaco
2025-10-20 13:43           ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 11/20] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
2025-10-02 11:03   ` Nam Cao
2025-10-02 11:17     ` Gabriele Monaco
2025-10-06 13:20       ` Nam Cao
2025-10-06 15:22         ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 12/20] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
2025-10-17  9:37   ` Nam Cao
2025-10-17  9:53     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 13/20] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
2025-10-10 13:46   ` Nam Cao
2025-10-13  8:33     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 14/20] rv: Add sample hybrid monitors stall Gabriele Monaco
2025-10-10 14:23   ` Nam Cao
2025-10-13  9:01     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 15/20] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
2025-10-10 14:29   ` Nam Cao
2025-10-13 14:14     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 16/20] sched: Export hidden tracepoints to modules Gabriele Monaco
2025-09-19 15:37   ` Steven Rostedt
2025-09-19 17:07     ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 17/20] sched: Add deadline tracepoints Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 18/20] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
2025-10-21 11:55   ` Nam Cao
2025-10-21 15:54     ` Gabriele Monaco
2025-10-23 12:36       ` Nam Cao
2025-10-24  9:20         ` Gabriele Monaco
2025-09-19 14:09 ` [PATCH v2 19/20] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
2025-10-21 12:00   ` Nam Cao
2025-09-19 14:09 ` [PATCH v2 20/20] rv: Add deadline monitors Gabriele Monaco
2025-10-10 15:04   ` Nam Cao
2025-10-13  7:30     ` Gabriele Monaco
2025-10-21 12:05 ` [PATCH v2 00/20] rv: Add Hybrid Automata monitor type, per-object and " Nam Cao
2025-10-21 15:45   ` Gabriele Monaco

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