* [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors
@ 2025-08-14 15:07 Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros Gabriele Monaco
` (16 more replies)
0 siblings, 17 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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).
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 (17):
rv: Refactor da_monitor to minimise macros
rv: Cleanup da_monitor after refactor
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/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: 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/rv-mon-stall.rst | 44 +
.../trace/rv/deterministic_automata.rst | 2 +-
Documentation/trace/rv/hybrid_automata.rst | 307 ++++
Documentation/trace/rv/index.rst | 1 +
Documentation/trace/rv/monitor_deadline.rst | 111 ++
Documentation/trace/rv/monitor_sched.rst | 62 +-
Documentation/trace/rv/monitor_stall.rst | 43 +
Documentation/trace/rv/monitor_synthesis.rst | 130 +-
include/linux/rv.h | 30 +
include/rv/automata.h | 132 +-
include/rv/da_common.h | 21 +
include/rv/da_monitor.h | 1265 ++++++++++-------
include/rv/ha_monitor.h | 398 ++++++
include/trace/events/sched.h | 55 +
kernel/sched/deadline.c | 8 +
kernel/trace/rv/Kconfig | 19 +
kernel/trace/rv/Makefile | 4 +
kernel/trace/rv/monitors/deadline/Kconfig | 5 +
kernel/trace/rv/monitors/deadline/deadline.c | 35 +
kernel/trace/rv/monitors/deadline/deadline.h | 82 ++
kernel/trace/rv/monitors/nomiss/Kconfig | 15 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 234 +++
kernel/trace/rv/monitors/nomiss/nomiss.h | 81 ++
.../trace/rv/monitors/nomiss/nomiss_trace.h | 19 +
kernel/trace/rv/monitors/nrp/nrp.c | 22 +-
kernel/trace/rv/monitors/nrp/nrp.h | 2 +
kernel/trace/rv/monitors/opid/Kconfig | 11 +-
kernel/trace/rv/monitors/opid/opid.c | 119 +-
kernel/trace/rv/monitors/opid/opid.h | 90 +-
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 | 18 +-
kernel/trace/rv/monitors/sco/sco.h | 2 +
kernel/trace/rv/monitors/scpd/scpd.c | 20 +-
kernel/trace/rv/monitors/scpd/scpd.h | 2 +
kernel/trace/rv/monitors/snep/snep.c | 20 +-
kernel/trace/rv/monitors/snep/snep.h | 2 +
kernel/trace/rv/monitors/snroc/snroc.c | 18 +-
kernel/trace/rv/monitors/snroc/snroc.h | 2 +
kernel/trace/rv/monitors/sssw/sssw.c | 30 +-
kernel/trace/rv/monitors/sssw/sssw.h | 2 +
kernel/trace/rv/monitors/stall/Kconfig | 9 +
kernel/trace/rv/monitors/stall/stall.c | 116 ++
kernel/trace/rv/monitors/stall/stall.h | 64 +
kernel/trace/rv/monitors/stall/stall_trace.h | 19 +
kernel/trace/rv/monitors/sts/sts.c | 26 +-
kernel/trace/rv/monitors/sts/sts.h | 2 +
kernel/trace/rv/monitors/throttle/Kconfig | 15 +
kernel/trace/rv/monitors/throttle/throttle.c | 259 ++++
kernel/trace/rv/monitors/throttle/throttle.h | 115 ++
.../rv/monitors/throttle/throttle_trace.h | 19 +
kernel/trace/rv/monitors/wip/wip.c | 18 +-
kernel/trace/rv/monitors/wip/wip.h | 2 +
kernel/trace/rv/monitors/wwnr/wwnr.c | 20 +-
kernel/trace/rv/monitors/wwnr/wwnr.h | 2 +
kernel/trace/rv/rv_trace.h | 68 +-
tools/verification/models/deadline/nomiss.dot | 23 +
.../verification/models/deadline/throttle.dot | 43 +
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 | 155 +-
tools/verification/rvgen/rvgen/dot2c.py | 129 +-
tools/verification/rvgen/rvgen/dot2k.py | 317 ++++-
tools/verification/rvgen/rvgen/generator.py | 4 +-
.../rvgen/rvgen/templates/container/main.c | 2 -
.../rvgen/rvgen/templates/dot2k/main.c | 17 +-
.../rvgen/templates/dot2k/trace_hybrid.h | 16 +
69 files changed, 3969 insertions(+), 1026 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/da_common.h
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: 0cc53520e68bea7fb80fdc6bdf8d226d1b6a98d9
--
2.50.1
^ permalink raw reply [flat|nested] 57+ messages in thread
* [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-21 8:14 ` Nam Cao
2025-08-14 15:07 ` [RFC PATCH 02/17] rv: Cleanup da_monitor after refactor Gabriele Monaco
` (15 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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 | 912 ++++++++++++-------------
kernel/trace/rv/monitors/nrp/nrp.c | 22 +-
kernel/trace/rv/monitors/nrp/nrp.h | 2 +
kernel/trace/rv/monitors/opid/opid.c | 32 +-
kernel/trace/rv/monitors/opid/opid.h | 2 +
kernel/trace/rv/monitors/sco/sco.c | 18 +-
kernel/trace/rv/monitors/sco/sco.h | 2 +
kernel/trace/rv/monitors/scpd/scpd.c | 20 +-
kernel/trace/rv/monitors/scpd/scpd.h | 2 +
kernel/trace/rv/monitors/snep/snep.c | 20 +-
kernel/trace/rv/monitors/snep/snep.h | 2 +
kernel/trace/rv/monitors/snroc/snroc.c | 18 +-
kernel/trace/rv/monitors/snroc/snroc.h | 2 +
kernel/trace/rv/monitors/sssw/sssw.c | 30 +-
kernel/trace/rv/monitors/sssw/sssw.h | 2 +
kernel/trace/rv/monitors/sts/sts.c | 26 +-
kernel/trace/rv/monitors/sts/sts.h | 2 +
kernel/trace/rv/monitors/wip/wip.c | 18 +-
kernel/trace/rv/monitors/wip/wip.h | 2 +
kernel/trace/rv/monitors/wwnr/wwnr.c | 20 +-
kernel/trace/rv/monitors/wwnr/wwnr.h | 2 +
23 files changed, 650 insertions(+), 642 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..bc02334aa8be 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -13,97 +13,102 @@
#include <rv/automata.h>
#include <linux/rv.h>
+#include <linux/stringify.h>
#include <linux/bug.h>
#include <linux/sched.h>
+#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
+#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
+static struct rv_monitor RV_MONITOR_NAME;
+
#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_MONITOR_NAME.react)
+ return;
+ RV_MONITOR_NAME.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_MONITOR_NAME.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 +120,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 +159,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..6bb252bfe980 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;
@@ -108,7 +106,7 @@ static void disable_nrp(void)
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 = {
@@ -116,7 +114,7 @@ static struct rv_monitor rv_nrp = {
.description = "need resched preempts.",
.enable = enable_nrp,
.disable = disable_nrp,
- .reset = da_monitor_reset_all_nrp,
+ .reset = da_monitor_reset_all,
.enabled = 0,
};
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..7e9f23a76867 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;
@@ -135,7 +133,7 @@ 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();
}
/*
@@ -146,7 +144,7 @@ static struct rv_monitor rv_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,
};
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..1f413f839cf3 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;
@@ -57,7 +55,7 @@ static void disable_sco(void)
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 = {
@@ -65,7 +63,7 @@ static struct rv_monitor rv_sco = {
.description = "scheduling context operations.",
.enable = enable_sco,
.disable = disable_sco,
- .reset = da_monitor_reset_all_sco,
+ .reset = da_monitor_reset_all,
.enabled = 0,
};
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..de032a9cbd3a 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;
@@ -65,7 +63,7 @@ static void disable_scpd(void)
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 = {
@@ -73,7 +71,7 @@ static struct rv_monitor rv_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,
};
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..5f90fec5ff5a 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;
@@ -65,7 +63,7 @@ static void disable_snep(void)
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 = {
@@ -73,7 +71,7 @@ static struct rv_monitor rv_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,
};
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..7e0a487d07ac 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;
@@ -54,7 +52,7 @@ static void disable_snroc(void)
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 = {
@@ -62,7 +60,7 @@ static struct rv_monitor rv_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,
};
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..8df599ccd7d8 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;
@@ -86,7 +84,7 @@ static void disable_sssw(void)
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 = {
@@ -94,7 +92,7 @@ static struct rv_monitor rv_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,
};
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..4d9d2fc3afb8 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;
@@ -123,7 +121,7 @@ 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();
}
/*
@@ -134,7 +132,7 @@ static struct rv_monitor rv_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,
};
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..394fd12e27b2 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;
@@ -57,7 +55,7 @@ static void disable_wip(void)
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 = {
@@ -65,7 +63,7 @@ static struct rv_monitor rv_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,
};
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..75d3a2a31f72 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;
@@ -56,7 +54,7 @@ static void disable_wwnr(void)
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 = {
@@ -64,7 +62,7 @@ static struct rv_monitor rv_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,
};
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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 02/17] rv: Cleanup da_monitor after refactor
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 03/17] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
` (14 subsequent siblings)
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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 | 107 ++++++++++++++++------------------------
2 files changed, 51 insertions(+), 80 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 bc02334aa8be..57a6e742d454 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>
@@ -23,33 +26,24 @@ static struct rv_monitor RV_MONITOR_NAME;
#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_MONITOR_NAME.react)
return;
RV_MONITOR_NAME.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)
+static void cond_react(enum states curr_state, enum events 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 - reset a monitor and setting it to init state
*/
@@ -100,7 +94,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;
@@ -111,6 +104,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
@@ -120,10 +114,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;
@@ -132,15 +124,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;
}
}
@@ -152,6 +146,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.
*
@@ -159,10 +154,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;
@@ -172,16 +166,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;
}
}
@@ -192,12 +186,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)
@@ -237,10 +231,10 @@ static inline void da_monitor_destroy(void)
return;
}
+#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
@@ -285,10 +279,10 @@ static inline void da_monitor_destroy(void)
return;
}
+#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
@@ -350,16 +344,16 @@ static inline void da_monitor_destroy(void)
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;
@@ -435,14 +429,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;
@@ -454,8 +447,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;
@@ -477,8 +469,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;
@@ -503,8 +495,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;
@@ -520,19 +512,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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 03/17] Documentation/rv: Adapt documentation after da_monitor refactoring
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 02/17] rv: Cleanup da_monitor after refactor Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 04/17] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
` (13 subsequent siblings)
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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
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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 04/17] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (2 preceding siblings ...)
2025-08-14 15:07 ` [RFC PATCH 03/17] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types Gabriele Monaco
` (12 subsequent siblings)
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, linux-trace-kernel
Cc: Gabriele Monaco, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/rvgen/dot2k.py | 6 ++++--
.../rvgen/rvgen/templates/dot2k/main.c | 17 +++++------------
2 files changed, 9 insertions(+), 14 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..f14a0e7ad56b 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;
@@ -57,7 +50,7 @@ static void disable_%%MODEL_NAME%%(void)
%%TRACEPOINT_DETACH%%
- da_monitor_destroy_%%MODEL_NAME%%();
+ da_monitor_destroy();
}
/*
@@ -68,7 +61,7 @@ static struct rv_monitor rv_%%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,
};
--
2.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (3 preceding siblings ...)
2025-08-14 15:07 ` [RFC PATCH 04/17] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-21 8:29 ` Nam Cao
2025-08-14 15:07 ` [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
` (11 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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 | 18 ++++++-------
tools/verification/rvgen/rvgen/dot2c.py | 30 +++++++++++-----------
tools/verification/rvgen/rvgen/dot2k.py | 18 ++++++-------
3 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index d9a3fe2b74bf..76d7a3cfaec6 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) -> 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()
@@ -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..e92567045c9c 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -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:
@@ -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:
@@ -140,7 +140,7 @@ class Dot2c(Automata):
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())
@@ -152,7 +152,7 @@ class Dot2c(Automata):
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..a48e32c4ca73 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"),
--
2.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (4 preceding siblings ...)
2025-08-14 15:07 ` [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-21 8:32 ` Nam Cao
2025-08-14 15:07 ` [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
` (10 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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.
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 e92567045c9c..1771a2c9f6b9 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):
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):
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):
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):
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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (5 preceding siblings ...)
2025-08-14 15:07 ` [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
@ 2025-08-14 15:07 ` Gabriele Monaco
2025-08-21 8:33 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type Gabriele Monaco
` (9 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:07 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.
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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (6 preceding siblings ...)
2025-08-14 15:07 ` [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-19 9:18 ` Juri Lelli
2025-08-21 12:18 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
` (8 subsequent siblings)
16 siblings, 2 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 26 +++
include/rv/da_monitor.h | 45 ++++-
include/rv/ha_monitor.h | 384 +++++++++++++++++++++++++++++++++++++
kernel/trace/rv/Kconfig | 13 ++
kernel/trace/rv/rv_trace.h | 63 ++++++
5 files changed, 526 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..6a7594080db1 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -83,11 +83,37 @@ 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
+
+/*
+ * Hybrid automaton per-object variables.
+ */
+struct ha_monitor {
+ struct da_monitor da_mon;
+ u64 env_store[MAX_HA_ENV_LEN];
+ struct hrtimer timer;
+};
+#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
+
+#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 57a6e742d454..d95ece5b7908 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -24,6 +24,33 @@
#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
static struct rv_monitor RV_MONITOR_NAME;
+/*
+ * 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
+
#ifdef CONFIG_RV_REACTORS
static void cond_react(enum states curr_state, enum events event)
@@ -51,6 +78,7 @@ 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_reset_hook(da_mon);
}
/*
@@ -63,6 +91,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);
}
/*
@@ -130,6 +159,9 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event)
return false;
}
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+ if (!da_monitor_event_hook(curr_state, event, next_state))
+ return false;
+
CONCATENATE(trace_event_, MONITOR_NAME)(
model_get_state_name(curr_state),
model_get_event_name(event),
@@ -171,6 +203,9 @@ static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
return false;
}
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+ if (!da_monitor_event_hook(tsk, curr_state, event, next_state))
+ return false;
+
CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
model_get_state_name(curr_state),
model_get_event_name(event),
@@ -196,14 +231,14 @@ static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
/*
* 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);
}
/*
@@ -239,14 +274,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;
}
/*
@@ -257,7 +292,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_mon = &per_cpu_ptr(&RV_DA_MON_NAME, cpu)->da_mon;
da_monitor_reset(da_mon);
}
}
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
new file mode 100644
index 000000000000..a7c7297a9759
--- /dev/null
+++ b/include/rv/ha_monitor.h
@@ -0,0 +1,384 @@
+/* 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>
+
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+static bool ha_monitor_handle_constraint(enum states curr_state, enum events event, enum states next_state);
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+static bool ha_monitor_handle_constraint(struct task_struct *tsk, enum states curr_state, enum events event, enum states next_state);
+#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 bool ha_cancel_timer(struct ha_monitor *ha_mon);
+#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>
+
+#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"
+
+#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_MONITOR_NAME.react)
+ return;
+ RV_MONITOR_NAME.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)
+{
+ return;
+}
+#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];
+}
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *timer);
+
+/* Should be supplied by the monitor */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env);
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state,
+ enum events event,
+ enum states next_state);
+
+/*
+ * 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++)
+ smp_store_mb(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);
+ if (unlikely(!ha_mon->timer.base))
+ hrtimer_setup(&ha_mon->timer, ha_monitor_timer_callback,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+}
+
+/*
+ * 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(ha_mon->timer.base))
+ 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;
+}
+
+/*
+ * ha_monitor_all_env_is_valid - return true if all env has been initialised
+ */
+static inline bool ha_monitor_all_env_is_valid(struct ha_monitor *ha_mon)
+{
+ for (int i = 0; i < ENV_MAX_STORED; i++) {
+ if (ha_monitor_env_invalid(ha_mon, i))
+ return false;
+ }
+ return true;
+}
+
+static inline void ha_get_env_string(struct seq_buf * s, struct ha_monitor *ha_mon)
+{
+ 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));
+ format_str = ",%s=%llu";
+ }
+}
+
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+
+/*
+ * ha_get_monitor - return the global or per-cpu monitor address
+ */
+static inline struct ha_monitor *ha_get_monitor(void)
+{
+ return to_ha_monitor(da_get_monitor());
+}
+
+/*
+ * 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(enum states curr_state,
+ enum events event,
+ enum states next_state)
+{
+ struct ha_monitor *ha_mon = ha_get_monitor();
+ DECLARE_SEQ_BUF(env_string, MAX_DA_NAME_LEN);
+ bool env_was_valid;
+
+ env_was_valid = ha_monitor_all_env_is_valid(ha_mon);
+
+ if (ha_verify_constraint(ha_mon, curr_state, event, next_state))
+ return true;
+
+ /*
+ * If we started with an uninitialised environment, chances are the
+ * failure is a false positive. Do not fail the monitor just this time.
+ * Note that we need to run ha_verify_constraint first as it might
+ * initialise the environment.
+ */
+ if (!env_was_valid)
+ return true;
+
+ ha_get_env_string(&env_string, ha_mon);
+ ha_cond_react(curr_state, event, env_string.buffer);
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(
+ model_get_state_name(curr_state),
+ model_get_event_name(event),
+ env_string.buffer);
+ return false;
+}
+
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *timer)
+{
+ struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer);
+ enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state);
+ DECLARE_SEQ_BUF(env_string, MAX_DA_NAME_LEN);
+
+ ha_get_env_string(&env_string, ha_mon);
+ ha_cond_react(curr_state, EVENT_NONE, env_string.buffer);
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(
+ model_get_state_name(curr_state),
+ EVENT_NONE_LBL,
+ env_string.buffer);
+
+ da_monitor_reset(&ha_mon->da_mon);
+
+ return HRTIMER_NORESTART;
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+
+/*
+ * ha_get_monitor - return the per-task monitor address
+ */
+static inline struct ha_monitor *ha_get_monitor(struct task_struct *tsk)
+{
+ return to_ha_monitor(da_get_monitor(tsk));
+}
+
+/*
+ * 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 task_struct *tsk,
+ enum states curr_state,
+ enum events event,
+ enum states next_state)
+{
+ struct ha_monitor *ha_mon = ha_get_monitor(tsk);
+ DECLARE_SEQ_BUF(env_string, 32);
+ bool env_was_valid;
+
+ env_was_valid = ha_monitor_all_env_is_valid(ha_mon);
+
+ if (ha_verify_constraint(ha_mon, curr_state, event, next_state))
+ return true;
+
+ /*
+ * If we started with an uninitialised environment, chances are the
+ * failure is a false positive. Do not fail the monitor just this time.
+ * Note that we need to run ha_verify_constraint first as it might
+ * initialise the environment.
+ */
+ if (!env_was_valid)
+ return true;
+
+ ha_get_env_string(&env_string, ha_mon);
+ ha_cond_react(curr_state, event, env_string.buffer);
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(tsk->pid,
+ model_get_state_name(curr_state),
+ model_get_event_name(event),
+ env_string.buffer);
+ return false;
+}
+
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *timer)
+{
+ struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer);
+ enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state);
+ DECLARE_SEQ_BUF(env_string, MAX_DA_NAME_LEN);
+ struct task_struct *tsk;
+
+ tsk = container_of(ha_mon, struct task_struct, rv[task_mon_slot].ha_mon);
+ ha_get_env_string(&env_string, ha_mon);
+ ha_cond_react(curr_state, EVENT_NONE, env_string.buffer);
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(tsk->pid,
+ model_get_state_name(curr_state),
+ EVENT_NONE_LBL,
+ env_string.buffer);
+
+ da_monitor_reset(&ha_mon->da_mon);
+
+ return HRTIMER_NORESTART;
+}
+
+#endif /* RV_MON_TYPE */
+
+/*
+ * 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)
+{
+ return ktime_get_ns() - READ_ONCE(ha_mon->env_store[env]);
+}
+static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env)
+{
+ smp_store_mb(ha_mon->env_store[env], ktime_get_ns());
+}
+
+/*
+ * 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 jiffies - READ_ONCE(ha_mon->env_store[env]);
+}
+static inline void ha_reset_clk_jiffy(struct ha_monitor *ha_mon, enum envs env)
+{
+ smp_store_mb(ha_mon->env_store[env], jiffies);
+}
+
+/*
+ * 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 inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire)
+{
+ int mode = HRTIMER_MODE_REL;
+ u64 passed = 0;
+
+ if (env >= 0 && env < ENV_MAX_STORED) {
+ if (ha_monitor_env_invalid(ha_mon, env))
+ return;
+ passed = ha_get_env(ha_mon, env);
+ }
+ if (RV_MON_TYPE == RV_MON_PER_CPU)
+ mode |= HRTIMER_MODE_PINNED;
+ hrtimer_start(&ha_mon->timer, ns_to_ktime(expire - passed), mode);
+}
+static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon,
+ enum envs env, u64 expire)
+{
+ u64 passed = 0;
+
+ if (env >= 0 && env < ENV_MAX_STORED) {
+ if (ha_monitor_env_invalid(ha_mon, env))
+ return;
+ passed = ha_get_env(ha_mon, env);
+ }
+ ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
+ jiffies_to_nsecs(expire - passed));
+}
+/*
+ * ha_cancel_timer - Cancel the timer and return whether it expired
+ *
+ * Return true if the timer was cancelled after expiration but before the
+ * callback could run.
+ */
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+ ktime_t remaining;
+
+ if (!hrtimer_active(&ha_mon->timer))
+ return false;
+ remaining = hrtimer_get_remaining(&ha_mon->timer);
+ if (hrtimer_try_to_cancel(&ha_mon->timer) == 1 && remaining < 0)
+ return true;
+ return false;
+}
+
+#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..dbb0cbbe15ca 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(
+ __array( char, state, MAX_DA_NAME_LEN )
+ __array( char, event, MAX_DA_NAME_LEN )
+ __array( char, env, MAX_DA_NAME_LEN )
+ ),
+
+ TP_fast_assign(
+ memcpy(__entry->state, state, MAX_DA_NAME_LEN);
+ memcpy(__entry->event, event, MAX_DA_NAME_LEN);
+ memcpy(__entry->env, env, MAX_DA_NAME_LEN);
+ ),
+
+ TP_printk("event %s not expected in the state %s with env %s\n",
+ __entry->event,
+ __entry->state,
+ __entry->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 )
+ __array( char, state, MAX_DA_NAME_LEN )
+ __array( char, event, MAX_DA_NAME_LEN )
+ __array( char, env, MAX_DA_NAME_LEN )
+ ),
+
+ TP_fast_assign(
+ memcpy(__entry->state, state, MAX_DA_NAME_LEN);
+ memcpy(__entry->event, event, MAX_DA_NAME_LEN);
+ memcpy(__entry->env, env, MAX_DA_NAME_LEN);
+ __entry->id = id;
+ ),
+
+ TP_printk("%d: event %s not expected in the state %s with env %s\n",
+ __entry->id,
+ __entry->event,
+ __entry->state,
+ __entry->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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (7 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-21 12:22 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
` (7 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 76d7a3cfaec6..c637cf4ee749 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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (8 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-21 12:38 ` Nam Cao
` (2 more replies)
2025-08-14 15:08 ` [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
` (6 subsequent siblings)
16 siblings, 3 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 128 +++++++-
tools/verification/rvgen/rvgen/dot2c.py | 64 +++-
tools/verification/rvgen/rvgen/dot2k.py | 273 +++++++++++++++++-
tools/verification/rvgen/rvgen/generator.py | 2 +
.../rvgen/rvgen/templates/dot2k/main.c | 2 +-
.../rvgen/templates/dot2k/trace_hybrid.h | 16 +
7 files changed, 475 insertions(+), 18 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 c637cf4ee749..c6525b95b15c 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -9,24 +9,50 @@
# Documentation/trace/rv/deterministic_automata.rst
import ntpath
+import re
+from typing import Iterator
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)
@@ -121,6 +147,7 @@ class Automata:
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 +161,77 @@ class Automata:
# so split them.
for i in event.split("\\n"):
+ if ";" in i:
+ # if the event contains a constraint (hybrid automata),
+ # it will be separated by a ";":
+ # "sched_switch;x<1000;reset(x)"
+ line = i.split(";")
+ i = line.pop(0)
+ if len(line) > 2:
+ raise ValueError("Only 1 constraint and 1 reset are supported")
+ envs += self.__extract_env_var(line)
events.append(i)
+ 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]
+ if "\\n" in state:
+ line = state.split("\\n")
+ line.pop(0)
+ if len(line) > 1:
+ raise ValueError("Only 1 constraint is supported in the state")
+ envs += self.__extract_env_var([line[0].replace(" ", "")])
cursor += 1
- return sorted(set(events))
-
- def __create_matrix(self):
+ 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[tuple[int, int] |
+ int, list[str]]]:
# transform the array into a dictionary
events = self.events
states = self.states
@@ -157,6 +249,7 @@ class Automata:
# declare the matrix....
matrix = [[ self.invalid_state_str for x in range(nr_event)] for y in range(nr_state)]
+ constraints = {}
# and we are back! Let's fill the matrix
cursor = self.__get_cursor_begin_events()
@@ -168,10 +261,23 @@ 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"):
+ if ";" in event:
+ line = event.split(";")
+ event = line.pop(0)
+ constraints[states_dict[origin_state],events_dict[event]] = line
+ # those events reset also on self loops
+ if origin_state == dest_state and "reset" in "".join(line):
+ 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]
+ if "\\n" in state:
+ line = state.replace(" ", "").split("\\n")
+ state = line.pop(0)
+ constraints[states_dict[state]] = line
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 +309,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: tuple[int, int] | int) -> 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, tuple)
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 1771a2c9f6b9..58b3ee70033a 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"
@@ -26,7 +27,7 @@ class Dot2c(Automata):
super().__init__(file_path, model_name)
self.line_length = 100
- 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:
@@ -36,7 +37,7 @@ class Dot2c(Automata):
return buff
- def format_states_enum(self):
+ def format_states_enum(self) -> list[str]:
buff = []
buff.append("enum %s {" % self.enum_states_def)
buff += self.__get_enum_states_content()
@@ -58,7 +59,7 @@ class Dot2c(Automata):
return buff
- def format_events_enum(self):
+ def format_events_enum(self) -> list[str]:
buff = []
buff.append("enum %s {" % self.enum_events_def)
buff += self.__get_enum_events_content()
@@ -66,7 +67,43 @@ class Dot2c(Automata):
return buff
- def get_minimun_type(self):
+ 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 = []
+ first = True
+ # 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:
+ if first:
+ buff.append("\t%s%s = 0," % (env, self.enum_suffix))
+ first = False
+ else:
+ 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");\n' %
+ (self.enum_suffix))
+
+ return buff
+
+ def get_minimun_type(self) -> str:
min_type = "unsigned char"
if self.states.__len__() > 255:
@@ -86,6 +123,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))
@@ -110,7 +149,7 @@ class Dot2c(Automata):
return string
- 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_string_vector_per_line_content(self.events))
@@ -125,7 +164,18 @@ class Dot2c(Automata):
return buff
- def __get_max_strlen_of_states(self):
+ 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__())
@@ -217,10 +267,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 a48e32c4ca73..1f6ad11117ac 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -20,12 +20,14 @@ 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()
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 +79,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 +110,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,7 +126,11 @@ class dot2k(Monitor, Dot2c):
buff.append(" TP_ARGS(%s)" % tp_args_c)
return '\n'.join(buff)
- def fill_main_c(self):
+ def fill_hybrid_definitions(self) -> list:
+ """Stub, not valid for deterministic automata"""
+ return []
+
+ def fill_main_c(self) -> str:
main_c = super().fill_main_c()
min_type = self.get_minimun_type()
@@ -127,5 +140,261 @@ 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_state_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)" % (clock_type, rule["env"],
+ self.enum_suffix, value)
+
+ 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:
+ 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 = "ha_get_env(ha_mon, %s%s) %s %s" % (
+ rule["env"], self.enum_suffix, rule["op"], value)
+ else:
+ c = self.__parse_state_constraint(rule, value)
+ if sep:
+ c += f" {sep} "
+ rules.append(c)
+ if reset:
+ c = "ha_reset_env(ha_mon, %s%s)" % (reset["env"], self.enum_suffix)
+ resets.append(c)
+ if self.is_event_constraint(key):
+ res = (["res = " + "".join(rules)] if rules else []) + resets
+ self.constraints[key] = ";".join(res)
+ else:
+ self.constraints[key] = rules[0]
+
+ def __get_constr_condition(self) -> list[str]:
+ buff = []
+ _else = ""
+ for edge, constr in self.constraints.items():
+ # skip state constraints
+ if not self.is_event_constraint(edge):
+ continue
+ 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:
+ buff.append("\t}")
+ _else = "else "
+ return buff
+
+ def __get_state_constr_condition(self) -> list[str]:
+ buff = []
+ # do not even print this if no state constraint is present
+ if not [c for c in self.constraints.keys() if not self.is_event_constraint(c)]:
+ return buff
+
+ # normally leaving the state with a constraint doesn't touch the timer,
+ # but if that event does reset, we need to carry on with the checks
+ 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)
+ if len(conditions) > 1:
+ condition_str = f"({condition_str})"
+ buff.append("\n\tif (%s || !res)\n\t\treturn res;" % condition_str)
+
+ _else = ""
+ constrained_states = set()
+ for state, constr in self.constraints.items():
+ # skip event constraints
+ if self.is_event_constraint(state):
+ continue
+ constrained_states.add(self.states[state])
+ buff.append("\t%sif (next_state == %s%s)" %
+ (_else, self.states[state], self.enum_suffix))
+ buff.append("\t\t%s;" % constr)
+ _else = "else "
+ buff.append("\telse if (%s)\n\t\tres = !ha_cancel_timer(ha_mon);" %
+ " ||".join(["curr_state == %s%s" % (s, self.enum_suffix)
+ for s in constrained_states]))
+ return buff
+
+ def fill_constr_func(self) -> list[str]:
+ buff = []
+ if self.constraints:
+ buff.append(
+"""/*
+ * This function is used to validate state transitions.
+ *
+ * It is generated by parsing the model, there is usually no need to change it,
+ * unless conditions were incorrectly specified or too complex for the parser.
+ * If the monitor requires a timer, this function is responsible to arm it when
+ * the next state has a constraint and cancel it in any other case. Transitions
+ * to the same state never affect timers.
+ */
+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)
+{
+\tbool res = true;
+""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
+ buff += self.__get_constr_condition()
+ buff += self.__get_state_constr_condition()
+ buff.append("""\treturn res;
+}\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);"
+ 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);"
+ 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)\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)\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()
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 f14a0e7ad56b..c9335cd32fc2 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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (9 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-19 8:53 ` Juri Lelli
2025-08-14 15:08 ` [RFC PATCH 12/17] rv: Add sample hybrid monitors stall Gabriele Monaco
` (5 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 307 ++++++++++++++++++
Documentation/trace/rv/index.rst | 1 +
Documentation/trace/rv/monitor_synthesis.rst | 86 +++++
4 files changed, 395 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..ecfff26d65bd
--- /dev/null
+++ b/Documentation/trace/rv/hybrid_automata.rst
@@ -0,0 +1,307 @@
+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.
+
+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
+--------
+
+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.
+
+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.
+As this is not exactly what is 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::
+
+ /* 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)
+ return res;
+ if (next_state == enqueued && res)
+ start_timer(clk, threshold);
+ else
+ cancel_timer();
+ 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).
+Invariants defined in this way only make sense as clock expirations (e.g. *clk
+< threshold*).
+
+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): 278-292.
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..fd886d842693 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,91 @@ 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.
+
+Unless the monitor relies on complex constraints, ``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
+
+Constraint values can be specified in 3 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
+
+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.
+
+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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 12/17] rv: Add sample hybrid monitors stall
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (10 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
` (4 subsequent siblings)
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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/rv-mon-stall.rst | 44 +++++++
Documentation/trace/rv/monitor_stall.rst | 43 +++++++
kernel/trace/rv/Kconfig | 1 +
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/monitors/stall/Kconfig | 9 ++
kernel/trace/rv/monitors/stall/stall.c | 116 +++++++++++++++++++
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 ++++
10 files changed, 318 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/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/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..b19ba970c8af
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_STALL
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "stall monitor"
+ help
+ auto-generated
diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c
new file mode 100644
index 000000000000..c98ad9838146
--- /dev/null
+++ b/kernel/trace/rv/monitors/stall/stall.c
@@ -0,0 +1,116 @@
+// 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
+#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)
+{
+ 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)
+{
+ if (env == clk_stall)
+ ha_reset_clk_jiffy(ha_mon, env);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state)
+{
+ bool res = true;
+
+ if (curr_state == dequeued_stall && event == sched_wakeup_stall)
+ ha_reset_env(ha_mon, clk_stall);
+
+ if (next_state == curr_state || !res)
+ return res;
+ if (next_state == enqueued_stall)
+ ha_start_timer_jiffy(ha_mon, clk_stall, threshold_jiffies);
+ else if (curr_state == enqueued_stall)
+ res = !ha_cancel_timer(ha_mon);
+ return res;
+}
+
+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_stall.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_stall = {
+ .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_stall, NULL);
+}
+
+static void __exit unregister_stall(void)
+{
+ rv_unregister_monitor(&rv_stall);
+}
+
+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..89b32c97c455
--- /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 = 0,
+ enqueued_stall,
+ running_stall,
+ state_max_stall
+};
+
+#define INVALID_STATE state_max_stall
+
+enum events_stall {
+ sched_switch_in_stall = 0,
+ sched_switch_wait_stall,
+ sched_wakeup_stall,
+ event_max_stall
+};
+
+enum envs_stall {
+ clk_stall = 0,
+ 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 dbb0cbbe15ca..3d9a4c70f523 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,
__entry->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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (11 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 12/17] rv: Add sample hybrid monitors stall Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-09-02 9:28 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 14/17] sched: Add deadline tracepoints Gabriele Monaco
` (3 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 109 ++++++---------------
kernel/trace/rv/monitors/opid/opid.h | 88 ++++-------------
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, 77 insertions(+), 235 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 7e9f23a76867..396841106aa4 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -10,94 +10,53 @@
#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)
-{
- da_handle_event(irq_entry_opid);
-}
-
-static void attach_vector_irq(void)
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_opid env)
{
- 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)
-{
- 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);
- }
-}
-
-#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)
-{
- da_handle_event(irq_disable_opid);
-}
-
-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)
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state)
{
- da_handle_event(preempt_enable_opid);
+ bool res = true;
+
+ if (curr_state == any_opid && event == sched_need_resched_opid)
+ res = ha_get_env(ha_mon, irq_off_opid) == 1ull;
+ else if (curr_state == any_opid && event == sched_waking_opid)
+ res = ha_get_env(ha_mon, irq_off_opid) == 1ull &&
+ ha_get_env(ha_mon, preempt_off_opid) == 1ull;
+ return res;
}
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 +67,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 +77,8 @@ static void disable_opid(void)
{
rv_opid.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 5014f1b85ecf..7c39641c65eb 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 = 0,
- enabled_opid,
- in_irq_opid,
- irq_disabled_opid,
- preempt_disabled_opid,
+ any_opid = 0,
state_max_opid
};
#define INVALID_STATE state_max_opid
enum events_opid {
- irq_disable_opid = 0,
- irq_enable_opid,
- irq_entry_opid,
- preempt_disable_opid,
- preempt_enable_opid,
- sched_need_resched_opid,
+ sched_need_resched_opid = 0,
sched_waking_opid,
event_max_opid
};
+enum envs_opid {
+ irq_off_opid = 0,
+ 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 3d9a4c70f523..601b03179328 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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (12 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-19 9:56 ` Juri Lelli
2025-08-14 15:08 ` [RFC PATCH 15/17] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
` (2 subsequent siblings)
16 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 55 ++++++++++++++++++++++++++++++++++++
kernel/sched/deadline.c | 8 ++++++
2 files changed, 63 insertions(+)
diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
index 7b2645b50e78..f34cc1dc4a13 100644
--- a/include/trace/events/sched.h
+++ b/include/trace/events/sched.h
@@ -609,6 +609,45 @@ TRACE_EVENT(sched_pi_setprio,
__entry->oldprio, __entry->newprio)
);
+/*
+DECLARE_EVENT_CLASS(sched_dl_template,
+
+ TP_PROTO(struct sched_dl_entity *dl),
+
+ TP_ARGS(dl),
+
+ TP_STRUCT__entry(
+ __field( struct task_struct *, tsk )
+ __string( comm, dl->dl_server ? "server" : container_of(dl, struct task_struct, dl)->comm )
+ __field( pid_t, pid )
+ __field( s64, runtime )
+ __field( u64, deadline )
+ __field( int, dl_yielded )
+ ),
+
+ TP_fast_assign(
+ __assign_str(comm);
+ __entry->pid = dl->dl_server ? -1 : container_of(dl, struct task_struct, dl)->pid;
+ __entry->runtime = dl->runtime;
+ __entry->deadline = dl->deadline;
+ __entry->dl_yielded = dl->dl_yielded;
+ ),
+
+ TP_printk("comm=%s pid=%d runtime=%lld deadline=%lld yielded=%d",
+ __get_str(comm), __entry->pid,
+ __entry->runtime, __entry->deadline,
+ __entry->dl_yielded)
+);
+
+DEFINE_EVENT(sched_dl_template, sched_dl_throttle,
+ TP_PROTO(struct sched_dl_entity *dl),
+ TP_ARGS(dl));
+
+DEFINE_EVENT(sched_dl_template, sched_dl_replenish,
+ TP_PROTO(struct sched_dl_entity *dl),
+ TP_ARGS(dl));
+*/
+
#ifdef CONFIG_DETECT_HUNG_TASK
TRACE_EVENT(sched_process_hang,
TP_PROTO(struct task_struct *tsk),
@@ -896,6 +935,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),
+ TP_ARGS(dl));
+
+DECLARE_TRACE(sched_dl_replenish,
+ TP_PROTO(struct sched_dl_entity *dl),
+ TP_ARGS(dl));
+
+DECLARE_TRACE(sched_dl_server_start,
+ TP_PROTO(struct sched_dl_entity *dl),
+ TP_ARGS(dl));
+
+DECLARE_TRACE(sched_dl_server_stop,
+ TP_PROTO(struct sched_dl_entity *dl, bool hard),
+ TP_ARGS(dl, hard));
+
#endif /* _TRACE_SCHED_H */
/* This part must be outside protection */
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index e2d51f4306b3..f8284accb6b4 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);
}
/*
@@ -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);
}
if (dl_se->dl_yielded)
@@ -1482,6 +1486,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);
dl_se->dl_throttled = 1;
/* If requested, inform the user about runtime overruns. */
@@ -1590,6 +1595,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);
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))
@@ -1601,6 +1607,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, true);
dequeue_dl_entity(dl_se, DEQUEUE_SLEEP);
hrtimer_try_to_cancel(&dl_se->dl_timer);
dl_se->dl_defer_armed = 0;
@@ -1618,6 +1625,7 @@ static bool dl_server_stopped(struct sched_dl_entity *dl_se)
return true;
}
+ trace_sched_dl_server_stop_tp(dl_se, false);
dl_se->dl_server_idle = 1;
return false;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 15/17] rv: Add support for per-object monitors in DA/HA
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (13 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 14/17] sched: Add deadline tracepoints Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 16/17] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 17/17] rv: Add deadline monitors Gabriele Monaco
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 most
of the implementation with the per-task monitors. Specifically, the user
needs to define how to get an id for the object (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. 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 | 2 +-
include/rv/da_common.h | 21 +++
include/rv/da_monitor.h | 325 +++++++++++++++++++++++++++++++++++++---
include/rv/ha_monitor.h | 36 +++--
4 files changed, 352 insertions(+), 32 deletions(-)
create mode 100644 include/rv/da_common.h
diff --git a/include/linux/rv.h b/include/linux/rv.h
index 6a7594080db1..6c49db8d4d07 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>
@@ -100,7 +101,6 @@ struct ha_monitor {
u64 env_store[MAX_HA_ENV_LEN];
struct hrtimer timer;
};
-#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
#else
diff --git a/include/rv/da_common.h b/include/rv/da_common.h
new file mode 100644
index 000000000000..67d817aee18e
--- /dev/null
+++ b/include/rv/da_common.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Red Hat, Inc.
+ *
+ * Common definitions for DA and HA monitors.
+ */
+#ifndef _RV_DA_COMMON_H
+#define _RV_DA_COMMON_H
+
+#include <linux/sched.h>
+
+/*
+ * ID monitor types (per-task and per-object) have an opaque type, this is
+ * defined by default for the per-task case but must be defined by the monitor
+ * in case of per-object monitors.
+ */
+#if RV_MON_TYPE == RV_MON_PER_TASK
+typedef struct task_struct *monitor_target;
+#endif
+
+#endif /* _RV_DA_COMMON_H */
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index d95ece5b7908..737bfeb2b48f 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -14,11 +14,13 @@
#ifndef _RV_DA_MONITOR_H
#define _RV_DA_MONITOR_H
+#include <rv/da_common.h>
#include <rv/automata.h>
#include <linux/rv.h>
#include <linux/stringify.h>
#include <linux/bug.h>
-#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/hashtable.h>
#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
@@ -51,6 +53,38 @@ static struct rv_monitor RV_MONITOR_NAME;
#define da_monitor_reset_hook(da_mon)
#endif
+/*
+ * Hook to allow the implementation of per-obj monitors: define it with a
+ * function that takes the object and a da_mon (can be NULL) and returns the
+ * (newly created) da_monitor for the objects.
+ * The monitor can run allocation manually if the start condition is in a
+ * context potentially problematic for allocation (e.g. while scheduling).
+ * In such case, if the storage was pre-allocated without a target, set it now.
+ */
+#if !defined(da_monitor_start_hook) && RV_MON_TYPE == RV_MON_PER_OBJ
+#ifdef DA_SKIP_AUTO_ALLOC
+static inline struct da_monitor *
+da_fill_empty_storage(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
+static struct da_monitor *da_create_storage(monitor_target target, struct da_monitor *da_mon);
+#endif /* DA_SKIP_AUTO_ALLOC */
+#else
+#define da_monitor_start_hook(target,da_mon) (da_mon)
+#endif
+
+/*
+ * Define a guard (e.g. lock/RCU) for access to the da_monitor.
+ * This is used to synchronise readers (e.g. da_get_monitor) but also writers
+ * (da_create_storage and da_destroy_storage) against da_monitor_destroy.
+ */
+#if !defined(da_guard_monitor) && RV_MON_TYPE == RV_MON_PER_OBJ
+#define da_guard_monitor() guard(rcu)()
+#else
+#define da_guard_monitor()
+#endif
+
#ifdef CONFIG_RV_REACTORS
static void cond_react(enum states curr_state, enum events event)
@@ -178,16 +212,21 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event)
return false;
}
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
/*
- * Event handler for per_task monitors.
+ * Event handler for per_task/per_object 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,
+#ifndef da_id_type
+#define da_id_type int
+#endif
+static inline da_id_type da_get_id(monitor_target target);
+
+static inline bool da_event(struct da_monitor *da_mon, monitor_target target,
enum events event)
{
enum states curr_state, next_state;
@@ -197,16 +236,16 @@ static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
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,
+ CONCATENATE(trace_error_, MONITOR_NAME)(da_get_id(target),
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))) {
- if (!da_monitor_event_hook(tsk, curr_state, event, next_state))
+ if (!da_monitor_event_hook(target, curr_state, event, next_state))
return false;
- CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
+ CONCATENATE(trace_event_, MONITOR_NAME)(da_get_id(target),
model_get_state_name(curr_state),
model_get_event_name(event),
model_get_state_name(next_state),
@@ -333,6 +372,24 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
return &tsk->rv[task_mon_slot].da_mon;
}
+/*
+ * 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 task_struct, rv[task_mon_slot].da_mon);
+}
+
+/*
+ * da_get_id - return the id associated to the target
+ *
+ * For per-task monitors, the id is the task's PID.
+ */
+static inline da_id_type da_get_id(monitor_target target)
+{
+ return target->pid;
+}
+
static void da_monitor_reset_all(void)
{
struct task_struct *g, *p;
@@ -379,6 +436,208 @@ static inline void da_monitor_destroy(void)
task_mon_slot = RV_PER_TASK_MONITOR_INIT;
return;
}
+
+#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)
+{
+ struct da_monitor_storage *mon_storage;
+
+ // TODO perhaps no need for the NOWAIT if not auto allocating
+ // NOT from tracepoints! Perhaps get it as an argument..
+ mon_storage = kmem_cache_zalloc(da_monitor_cache, GFP_NOWAIT);
+ 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(monitor_target target, struct da_monitor *da_mon)
+{
+ struct da_monitor_storage *mon_storage;
+ da_id_type id = da_get_id(target);
+
+ if (da_mon)
+ return da_mon;
+
+ mon_storage = da_create_empty_storage(id);
+ 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(monitor_target target)
+{
+ struct da_monitor_storage *mon_storage;
+
+ mon_storage = __da_get_mon_storage(da_get_id(target));
+ 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_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(monitor_target target)
+{
+ da_guard_monitor();
+ da_create_storage(target, da_get_monitor(target));
+}
+
+/*
+ * 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(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;
+
+ da_guard_monitor();
+ 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(monitor_target target)
+{
+ struct da_monitor_storage *mon_storage;
+
+ da_guard_monitor();
+ mon_storage = __da_get_mon_storage(da_get_id(target));
+
+ if (!mon_storage)
+ return;
+ 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
@@ -464,17 +723,17 @@ static inline bool da_handle_start_run_event(enum events event)
return 1;
}
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
/*
- * Handle event for per task.
+ * Handle event for per task/object.
*/
static inline void __da_handle_event(struct da_monitor *da_mon,
- struct task_struct *tsk, enum events event)
+ monitor_target target, enum events event)
{
bool retval;
- retval = da_event(da_mon, tsk, event);
+ retval = da_event(da_mon, target, event);
if (!retval)
da_monitor_reset(da_mon);
}
@@ -482,16 +741,22 @@ static inline void __da_handle_event(struct da_monitor *da_mon,
/*
* 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(monitor_target target, enum events event)
{
- struct da_monitor *da_mon = da_get_monitor(tsk);
+ struct da_monitor *da_mon;
bool retval;
+ da_guard_monitor();
+ da_mon = da_get_monitor(target);
+
+ if (unlikely(!da_mon))
+ return;
+
retval = da_monitor_handling_event(da_mon);
if (!retval)
return;
- __da_handle_event(da_mon, tsk, event);
+ __da_handle_event(da_mon, target, event);
}
/*
@@ -504,7 +769,7 @@ static inline void 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,
+static inline bool da_handle_start_event(monitor_target target,
enum events event)
{
struct da_monitor *da_mon;
@@ -512,14 +777,19 @@ static inline bool da_handle_start_event(struct task_struct *tsk,
if (!da_monitor_enabled())
return 0;
- da_mon = da_get_monitor(tsk);
+ da_guard_monitor();
+ da_mon = da_get_monitor(target);
+ da_mon = da_monitor_start_hook(target, da_mon);
+
+ if (unlikely(!da_mon))
+ return 0;
if (unlikely(!da_monitoring(da_mon))) {
da_monitor_start(da_mon);
return 0;
}
- __da_handle_event(da_mon, tsk, event);
+ __da_handle_event(da_mon, target, event);
return 1;
}
@@ -530,7 +800,7 @@ static inline bool da_handle_start_event(struct task_struct *tsk,
* 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,
+static inline bool da_handle_start_run_event(monitor_target target,
enum events event)
{
struct da_monitor *da_mon;
@@ -538,15 +808,30 @@ static inline bool da_handle_start_run_event(struct task_struct *tsk,
if (!da_monitor_enabled())
return 0;
- da_mon = da_get_monitor(tsk);
+ da_guard_monitor();
+ da_mon = da_get_monitor(target);
+ da_mon = da_monitor_start_hook(target, da_mon);
+
+ if (unlikely(!da_mon))
+ return 0;
if (unlikely(!da_monitoring(da_mon)))
da_monitor_start(da_mon);
- __da_handle_event(da_mon, tsk, event);
+ __da_handle_event(da_mon, target, event);
return 1;
}
+
+static inline void da_reset(monitor_target target)
+{
+ struct da_monitor *da_mon;
+
+ da_guard_monitor();
+ da_mon = da_get_monitor(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 a7c7297a9759..0c7ab52214c0 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -19,11 +19,12 @@
#define _RV_HA_MONITOR_H
#include <rv/automata.h>
+#include <rv/da_common.h>
#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
static bool ha_monitor_handle_constraint(enum states curr_state, enum events event, enum states next_state);
-#elif RV_MON_TYPE == RV_MON_PER_TASK
-static bool ha_monitor_handle_constraint(struct task_struct *tsk, enum states curr_state, enum events event, enum states next_state);
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+static bool ha_monitor_handle_constraint(monitor_target target, enum states curr_state, enum events event, enum states next_state);
#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);
@@ -35,6 +36,11 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon);
#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) ((struct ha_monitor *)da)
+
#define ENV_MAX CONCATENATE(env_max_, MONITOR_NAME)
#define ENV_MAX_STORED CONCATENATE(env_max_stored_, MONITOR_NAME)
#define envs CONCATENATE(envs_, MONITOR_NAME)
@@ -230,14 +236,23 @@ static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *timer)
return HRTIMER_NORESTART;
}
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
/*
* ha_get_monitor - return the per-task monitor address
*/
-static inline struct ha_monitor *ha_get_monitor(struct task_struct *tsk)
+static inline struct ha_monitor *ha_get_monitor(monitor_target target)
+{
+ return to_ha_monitor(da_get_monitor(target));
+}
+
+/*
+ * ha_get_target - return the object associated to the monitor
+ */
+static inline monitor_target ha_get_target(struct ha_monitor *ha_mon)
{
- return to_ha_monitor(da_get_monitor(tsk));
+ /* tasks have a union, the offset of da_mon and ha_mon are the same */
+ return da_get_target((struct da_monitor *)ha_mon);
}
/*
@@ -248,12 +263,12 @@ static inline struct ha_monitor *ha_get_monitor(struct task_struct *tsk)
* 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 task_struct *tsk,
+static bool ha_monitor_handle_constraint(monitor_target target,
enum states curr_state,
enum events event,
enum states next_state)
{
- struct ha_monitor *ha_mon = ha_get_monitor(tsk);
+ struct ha_monitor *ha_mon = ha_get_monitor(target);
DECLARE_SEQ_BUF(env_string, 32);
bool env_was_valid;
@@ -273,7 +288,7 @@ static bool ha_monitor_handle_constraint(struct task_struct *tsk,
ha_get_env_string(&env_string, ha_mon);
ha_cond_react(curr_state, event, env_string.buffer);
- CONCATENATE(trace_error_env_, MONITOR_NAME)(tsk->pid,
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(da_get_id(target),
model_get_state_name(curr_state),
model_get_event_name(event),
env_string.buffer);
@@ -285,12 +300,11 @@ static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *timer)
struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer);
enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state);
DECLARE_SEQ_BUF(env_string, MAX_DA_NAME_LEN);
- struct task_struct *tsk;
+ monitor_target target = ha_get_target(ha_mon);
- tsk = container_of(ha_mon, struct task_struct, rv[task_mon_slot].ha_mon);
ha_get_env_string(&env_string, ha_mon);
ha_cond_react(curr_state, EVENT_NONE, env_string.buffer);
- CONCATENATE(trace_error_env_, MONITOR_NAME)(tsk->pid,
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(da_get_id(target),
model_get_state_name(curr_state),
EVENT_NONE_LBL,
env_string.buffer);
--
2.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 16/17] verification/rvgen: Add support for per-obj monitors
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (14 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 15/17] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 17/17] rv: Add deadline monitors Gabriele Monaco
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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 | 20 ++++++++++++++++++++
tools/verification/rvgen/rvgen/generator.py | 2 +-
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index 1f6ad11117ac..951bdb893592 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -23,10 +23,14 @@ class dot2k(Monitor, Dot2c):
self.monitor_class = extra_params["monitor_class"]
def fill_monitor_type(self) -> str:
+ if self.monitor_type == "per_obj":
+ return self.monitor_type.upper() + """
+typedef /* XXX: define the target type */ *monitor_target;"""
return self.monitor_type.upper()
def fill_tracepoint_handlers_skel(self) -> str:
buff = []
+ buff += self.fill_per_obj_definitions()
buff += self.fill_hybrid_definitions()
for event in self.events:
buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
@@ -41,6 +45,9 @@ 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("\tmonitor_target t = /* XXX: how do I get t? */;");
+ buff.append("\tda_%s(t, %s%s);" % (handle, event, self.enum_suffix));
else:
buff.append("\tda_%s(%s%s);" % (handle, event, self.enum_suffix));
buff.append("}")
@@ -130,6 +137,19 @@ class dot2k(Monitor, Dot2c):
"""Stub, not valid for deterministic automata"""
return []
+ def fill_per_obj_definitions(self) -> list:
+ if self.monitor_type == "per_obj":
+ return ["""
+/*
+ * da_get_id - Get the id from a target
+ */
+static inline da_id_type da_get_id(monitor_target target)
+{
+ return /* XXX: define how to get an id from the target */;
+}
+"""]
+ return []
+
def fill_main_c(self) -> str:
main_c = super().fill_main_c()
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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* [RFC PATCH 17/17] rv: Add deadline monitors
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
` (15 preceding siblings ...)
2025-08-14 15:08 ` [RFC PATCH 16/17] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
@ 2025-08-14 15:08 ` Gabriele Monaco
16 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-14 15:08 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/monitor_deadline.rst | 111 ++++++++
kernel/trace/rv/Kconfig | 5 +
kernel/trace/rv/Makefile | 3 +
kernel/trace/rv/monitors/deadline/Kconfig | 5 +
kernel/trace/rv/monitors/deadline/deadline.c | 35 +++
kernel/trace/rv/monitors/deadline/deadline.h | 82 ++++++
kernel/trace/rv/monitors/nomiss/Kconfig | 15 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 234 ++++++++++++++++
kernel/trace/rv/monitors/nomiss/nomiss.h | 81 ++++++
.../trace/rv/monitors/nomiss/nomiss_trace.h | 19 ++
kernel/trace/rv/monitors/throttle/Kconfig | 15 +
kernel/trace/rv/monitors/throttle/throttle.c | 259 ++++++++++++++++++
kernel/trace/rv/monitors/throttle/throttle.h | 115 ++++++++
.../rv/monitors/throttle/throttle_trace.h | 19 ++
kernel/trace/rv/rv_trace.h | 2 +
tools/verification/models/deadline/nomiss.dot | 23 ++
.../verification/models/deadline/throttle.dot | 43 +++
17 files changed, 1066 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/monitor_deadline.rst b/Documentation/trace/rv/monitor_deadline.rst
new file mode 100644
index 000000000000..af144605bbb0
--- /dev/null
+++ b/Documentation/trace/rv/monitor_deadline.rst
@@ -0,0 +1,111 @@
+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::
+
+ |
+ |
+ dl_replenish;reset(clk) v
+ sched_switch_in #=========================# sched_switch_in;
+ +--------------- H H reset(clk)
+ | H H <----------------+
+ +--------------> H running H |
+ dl_throttle H clk < runtime_left_ns() H |
+ +---------------------------- H H sched_switch_out |
+ | +--------------------> H H -------------+ |
+ | dl_replenish; #=========================# | |
+ | reset(clk) | ^ | |
+ | | dl_defer_arm sched_switch_in; | |
+ | | | reset(clk) | |
+ v | v | | |
+ +------------+ dl_replenish +----------------+ | |
+ | | dl_defer_arm | | sched_switch_out | |
+ | throttled | +---------- | armed | -------------+ | |
+ | | | | | <--------+ | | |
+ +------------+ +---------> | | 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_replenish
+ | | | | |
+ | +-------------> | preempted_throttled | --------+
+ | | |
+ +-----------------------------> | |
+ sched_switch_out +-------------------------+
+
+
+Monitor nomiss
+~~~~~~~~~~~~~~
+
+The nomiss monitor ensures dl entities run to completion before their
+deadiline. An entity is considered done if throttled, either because it yielded
+or used up its runtime, or when it goes to sleep.
+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::
+
+ sched_switch_in
+ sched_wakeup
+ +----------------------+
+ v |
+ #==========================# sched_switch_suspend
+ --------> H H ----------------+
+ H H v
+ H H +----------+
+ H H | sleeping |
+ H running H +----------+
+ H clk < DEADLINE_LEFT_NS() H sched_wakeup; |
+ H H reset(clk) |
+ H H <---------------+
+ +-----------------> H H -+
+ | #==========================# |
+ | |
+ | sched_switch_suspend |
+ sched_switch_in dl_throttle |
+ sched_wakeup;reset(clk) +----------------------+ | dl_throttle
+ | v | |
+ | +--------------------------+ |
+ +------------------ | throttled | <+
+ +--------------------------+
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..652876730a39
--- /dev/null
+++ b/kernel/trace/rv/monitors/deadline/Kconfig
@@ -0,0 +1,5 @@
+config RV_MON_DEADLINE
+ depends on RV
+ bool "deadline monitor"
+ help
+ auto-generated
diff --git a/kernel/trace/rv/monitors/deadline/deadline.c b/kernel/trace/rv/monitors/deadline/deadline.c
new file mode 100644
index 000000000000..61564fbbe333
--- /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 = "auto-generated",
+ .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("dot2k: auto-generated");
+MODULE_DESCRIPTION("deadline: auto-generated");
diff --git a/kernel/trace/rv/monitors/deadline/deadline.h b/kernel/trace/rv/monitors/deadline/deadline.h
new file mode 100644
index 000000000000..20f51e1de866
--- /dev/null
+++ b/kernel/trace/rv/monitors/deadline/deadline.h
@@ -0,0 +1,82 @@
+/* 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;
+
+/*
+ * 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;
+}
+
+static inline int get_server_id(void)
+{
+ /*
+ * Use negative numbers for the server.
+ * Currently only one fair server per CPU, may change in the future.
+ */
+ return -__smp_processor_id();
+}
+
+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;
+}
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..4b61a861a62c
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -0,0 +1,234 @@
+// 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>
+#include <monitors/deadline/deadline.h>
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+/* 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>
+
+/*
+ * da_get_id - Get the id from a dl server
+ *
+ * Deadline tasks use the task's PID, while fair servers use the negated cpu.
+ */
+static inline da_id_type da_get_id(monitor_target target)
+{
+ if (target->dl_server)
+ return get_server_id();
+ return container_of(target, struct task_struct, dl)->pid;
+}
+
+/*
+ * 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.
+ */
+static u64 deadline_thresh = 0;
+module_param(deadline_thresh, ullong, 0644);
+#define DEADLINE_LEFT_NS(ha_mon) (ha_get_target(ha_mon)->deadline + deadline_thresh)
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_nomiss env)
+{
+ if (env == clk_nomiss)
+ return ha_get_clk_ns(ha_mon, env);
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_nomiss env)
+{
+ if (env == clk_nomiss)
+ ha_reset_clk_ns(ha_mon, env);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state)
+{
+ bool res = true;
+
+ if (curr_state == sleeping_nomiss && event == sched_switch_in_nomiss)
+ ha_reset_env(ha_mon, clk_nomiss);
+ else if (curr_state == throttled_nomiss && event == sched_switch_in_nomiss)
+ ha_reset_env(ha_mon, clk_nomiss);
+
+ if (next_state == curr_state || !res)
+ return res;
+ if (next_state == running_nomiss)
+ ha_start_timer_ns(ha_mon, clk_nomiss, DEADLINE_LEFT_NS(ha_mon));
+ else if (curr_state == running_nomiss)
+ res = !ha_cancel_timer(ha_mon);
+ return res;
+}
+
+static void handle_dl_throttle(void *data, struct sched_dl_entity *dl)
+{
+ da_handle_event(dl, dl_throttle_nomiss);
+}
+
+static void handle_dl_server_start(void *data, struct sched_dl_entity *dl)
+{
+ da_handle_start_event(dl, sched_switch_in_nomiss);
+}
+
+static void handle_dl_server_stop(void *data, struct sched_dl_entity *dl, bool hard)
+{
+ if (hard)
+ da_handle_event(dl, sched_switch_suspend_nomiss);
+}
+
+static void handle_sched_switch(void *data, bool preempt, struct task_struct *prev, struct task_struct *next, unsigned int prev_state)
+{
+ if (prev_state != TASK_RUNNING && prev->policy == SCHED_DEADLINE)
+ da_handle_event(&prev->dl, sched_switch_suspend_nomiss);
+ if (next->policy == SCHED_DEADLINE)
+ da_handle_start_event(&next->dl, sched_switch_in_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(&p->dl);
+ else if (new_policy == SCHED_DEADLINE)
+ da_create_conditional(&p->dl);
+}
+
+static void handle_sched_wakeup(void *data, struct task_struct *tsk)
+{
+ if (tsk->policy == SCHED_DEADLINE)
+ da_handle_start_event(&tsk->dl, sched_wakeup_nomiss);
+}
+
+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(&task->dl, NULL);
+}
+
+static void handle_exit(void *data, struct task_struct *p, bool group_dead)
+{
+ if (p->policy == SCHED_DEADLINE)
+ da_destroy_storage(&p->dl);
+}
+
+/*
+ * 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) {
+ /* The servers' ids are determined according to da_get_id */
+ if (!da_create_empty_storage(-cpu))
+ goto fail;
+ }
+
+ for_each_process_thread(g, p) {
+ if (p->policy == SCHED_DEADLINE) {
+ if (!da_create_storage(&p->dl, NULL))
+ goto fail;
+ }
+ }
+ return 0;
+
+fail:
+ da_monitor_destroy();
+ return -ENOMEM;
+}
+
+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_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_nomiss.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_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_nomiss = {
+ .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_nomiss, &rv_deadline);
+}
+
+static void __exit unregister_nomiss(void)
+{
+ rv_unregister_monitor(&rv_nomiss);
+}
+
+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..a4059b34c60d
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.h
@@ -0,0 +1,81 @@
+/* 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 {
+ running_nomiss = 0,
+ sleeping_nomiss,
+ throttled_nomiss,
+ state_max_nomiss
+};
+
+#define INVALID_STATE state_max_nomiss
+
+enum events_nomiss {
+ dl_throttle_nomiss = 0,
+ sched_switch_in_nomiss,
+ sched_switch_suspend_nomiss,
+ sched_wakeup_nomiss,
+ event_max_nomiss
+};
+
+enum envs_nomiss {
+ clk_nomiss = 0,
+ env_max_nomiss,
+ env_max_stored_nomiss = env_max_nomiss
+};
+
+_Static_assert(env_max_stored_nomiss <= MAX_HA_ENV_LEN, "Not enough slots");
+
+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 = {
+ "running",
+ "sleeping",
+ "throttled"
+ },
+ .event_names = {
+ "dl_throttle",
+ "sched_switch_in",
+ "sched_switch_suspend",
+ "sched_wakeup"
+ },
+ .env_names = {
+ "clk"
+ },
+ .function = {
+ {
+ throttled_nomiss,
+ running_nomiss,
+ sleeping_nomiss,
+ running_nomiss
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ running_nomiss
+ },
+ {
+ throttled_nomiss,
+ running_nomiss,
+ throttled_nomiss,
+ running_nomiss
+ },
+ },
+ .initial_state = running_nomiss,
+ .final_states = { 1, 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..412b53b268f5
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/throttle.c
@@ -0,0 +1,259 @@
+// 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>
+#include <monitors/deadline/deadline.h>
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+/* 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>
+
+/*
+ * da_get_id - Get the id from a dl server
+ *
+ * Deadline tasks use the task's PID, while fair servers use the negated cpu.
+ */
+static inline da_id_type da_get_id(monitor_target target)
+{
+ if (target->dl_server)
+ return get_server_id();
+ return container_of(target, struct task_struct, dl)->pid;
+}
+
+/* with sched_feat(HRTICK_DL) the threshold should be lower */
+#define RUNTIME_THRESH jiffies_to_nsecs(1)
+
+static inline u64 runtime_left_ns(struct ha_monitor *ha_mon)
+{
+ return ha_get_target(ha_mon)->runtime + RUNTIME_THRESH;
+}
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_throttle env)
+{
+ if (env == clk_throttle)
+ return ha_get_clk_ns(ha_mon, env);
+ 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)
+{
+ if (env == clk_throttle)
+ ha_reset_clk_ns(ha_mon, env);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state)
+{
+ bool res = true;
+
+ if (curr_state == armed_throttle && event == sched_switch_in_throttle)
+ ha_reset_env(ha_mon, clk_throttle);
+ else if (curr_state == armed_throttled_throttle && event == dl_throttle_throttle)
+ res = ha_get_env(ha_mon, yielded_throttle) == 1ull;
+ else if (curr_state == preempted_throttle && event == sched_switch_in_throttle)
+ ha_reset_env(ha_mon, clk_throttle);
+ else if (curr_state == running_throttle && event == dl_replenish_throttle)
+ ha_reset_env(ha_mon, clk_throttle);
+ else if (curr_state == throttled_throttle && event == dl_replenish_throttle)
+ ha_reset_env(ha_mon, clk_throttle);
+
+ if ((next_state == curr_state && event != dl_replenish_throttle) || !res)
+ return res;
+ if (next_state == running_throttle)
+ ha_start_timer_ns(ha_mon, clk_throttle, runtime_left_ns(ha_mon));
+ else if (curr_state == running_throttle)
+ res = !ha_cancel_timer(ha_mon);
+ return res;
+}
+
+static void handle_dl_replenish(void *data, struct sched_dl_entity *dl)
+{
+ da_handle_event(dl, dl_replenish_throttle);
+}
+
+static void handle_dl_throttle(void *data, struct sched_dl_entity *dl)
+{
+ da_handle_event(dl, dl_throttle_throttle);
+}
+
+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(get_server_id());
+}
+
+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;
+
+ if (prev->policy == SCHED_DEADLINE)
+ da_handle_event(&prev->dl, sched_switch_out_throttle);
+ if (next->policy == SCHED_DEADLINE)
+ da_handle_start_event(&next->dl, sched_switch_in_throttle);
+
+ /*
+ * The server is available in next only if the next task is boosted,
+ * otherwise we need to retrieve it.
+ */
+ dl = get_fair_server(next);
+ if (!dl)
+ return;
+ if (next->dl_server)
+ da_handle_start_event(next->dl_server, sched_switch_in_throttle);
+ else if (is_idle_task(next) || next->policy == SCHED_NORMAL)
+ da_handle_event(dl, dl_defer_arm_throttle);
+ else
+ da_handle_event(dl, sched_switch_out_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(&p->dl);
+ /*
+ * 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)
+ return;
+ da_handle_event(dl, dl_defer_arm_throttle);
+ }
+ } else if (new_policy == SCHED_DEADLINE) {
+ da_create_conditional(&p->dl);
+ }
+}
+
+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(&task->dl, NULL);
+}
+
+static void handle_exit(void *data, struct task_struct *p, bool group_dead)
+{
+ if (p->policy == SCHED_DEADLINE)
+ da_destroy_storage(&p->dl);
+}
+
+/*
+ * 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) {
+ /* The servers' ids are determined according to da_get_id */
+ if (!da_create_empty_storage(-cpu))
+ goto fail;
+ }
+
+ for_each_process_thread(g, p) {
+ if (p->policy == SCHED_DEADLINE) {
+ if (!da_create_storage(&p->dl, NULL))
+ goto fail;
+ }
+ }
+ return 0;
+
+fail:
+ da_monitor_destroy();
+ return -ENOMEM;
+}
+
+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("throttle", sched_process_exit, handle_exit);
+
+ return 0;
+}
+
+static void disable_throttle(void)
+{
+ rv_throttle.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("throttle", sched_switch, handle_sched_switch);
+
+ da_monitor_destroy();
+}
+
+static struct rv_monitor rv_throttle = {
+ .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_throttle, &rv_deadline);
+}
+
+static void __exit unregister_throttle(void)
+{
+ rv_unregister_monitor(&rv_throttle);
+}
+
+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..084221556bae
--- /dev/null
+++ b/kernel/trace/rv/monitors/throttle/throttle.h
@@ -0,0 +1,115 @@
+/* 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 = 0,
+ 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 = 0,
+ dl_replenish_throttle,
+ dl_throttle_throttle,
+ sched_switch_in_throttle,
+ sched_switch_out_throttle,
+ event_max_throttle
+};
+
+enum envs_throttle {
+ clk_throttle = 0,
+ yielded_throttle,
+ env_max_throttle,
+ env_max_stored_throttle = yielded_throttle
+};
+
+_Static_assert(env_max_stored_throttle <= MAX_HA_ENV_LEN, "Not enough slots");
+
+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",
+ "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,
+ INVALID_STATE,
+ 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 601b03179328..40b19e2aa69c 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..81d7028cfc3b
--- /dev/null
+++ b/tools/verification/models/deadline/nomiss.dot
@@ -0,0 +1,23 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_running"};
+ {node [shape = doublecircle] "running"};
+ {node [shape = circle] "running"};
+ {node [shape = circle] "sleeping"};
+ {node [shape = circle] "throttled"};
+ "__init_running" -> "running";
+ "running" [label = "running\nclk < DEADLINE_LEFT_NS()", color = green3];
+ "running" -> "running" [ label = "sched_switch_in\nsched_wakeup" ];
+ "running" -> "sleeping" [ label = "sched_switch_suspend" ];
+ "running" -> "throttled" [ label = "dl_throttle" ];
+ "sleeping" [label = "sleeping"];
+ "sleeping" -> "running" [ label = "sched_wakeup;reset(clk)" ];
+ "throttled" [label = "throttled"];
+ "throttled" -> "running" [ label = "sched_switch_in\nsched_wakeup;reset(clk)" ];
+ "throttled" -> "throttled" [ label = "sched_switch_suspend\ndl_throttle" ];
+ { rank = min ;
+ "__init_running";
+ "running";
+ }
+}
diff --git a/tools/verification/models/deadline/throttle.dot b/tools/verification/models/deadline/throttle.dot
new file mode 100644
index 000000000000..2477115aa286
--- /dev/null
+++ b/tools/verification/models/deadline/throttle.dot
@@ -0,0 +1,43 @@
+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" -> "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" ];
+ "throttled" [label = "throttled"];
+ "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.50.1
^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata
2025-08-14 15:08 ` [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
@ 2025-08-19 8:53 ` Juri Lelli
2025-08-19 9:14 ` Juri Lelli
2025-08-19 10:40 ` Gabriele Monaco
0 siblings, 2 replies; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 8:53 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
linux-doc, Nam Cao, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
Hi!
On 14/08/25 17:08, Gabriele Monaco wrote:
> 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 | 307 ++++++++++++++++++
> Documentation/trace/rv/index.rst | 1 +
> Documentation/trace/rv/monitor_synthesis.rst | 86 +++++
> 4 files changed, 395 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..ecfff26d65bd
> --- /dev/null
> +++ b/Documentation/trace/rv/hybrid_automata.rst
> @@ -0,0 +1,307 @@
> +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.
> +
> +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
> +--------
Maybe add subsection titles to better mark separation between different
examples?
> +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.
> +
> +As a sample timed automaton we can define 'stall' as:
Maybe indicate this first one is a not properly correct example (correct
version follows)?
> +
> +- *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.
> +As this is not exactly what is 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::
> +
> + /* 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)
> + return res;
> + if (next_state == enqueued && res)
> + start_timer(clk, threshold);
So, then the timer callback checks the invariant and possibly reports
failure?
> + else
> + cancel_timer();
> + 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).
> +Invariants defined in this way only make sense as clock expirations (e.g. *clk
> +< threshold*).
Thanks,
Juri
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata
2025-08-19 8:53 ` Juri Lelli
@ 2025-08-19 9:14 ` Juri Lelli
2025-08-19 10:46 ` Gabriele Monaco
2025-08-19 10:40 ` Gabriele Monaco
1 sibling, 1 reply; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 9:14 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
linux-doc, Nam Cao, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On 19/08/25 10:53, Juri Lelli wrote:
> Hi!
>
> On 14/08/25 17:08, Gabriele Monaco wrote:
...
> > + 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)
> > + return res;
> > + if (next_state == enqueued && res)
> > + start_timer(clk, threshold);
>
> So, then the timer callback checks the invariant and possibly reports
> failure?
Ah, OK. The 'standard' ha_monitor_timer_callback just reports failure
(react) in case the timer fires. Which makes sense as at that point the
invariant is broken. Maybe add some wording to highlight this?
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-14 15:08 ` [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type Gabriele Monaco
@ 2025-08-19 9:18 ` Juri Lelli
2025-08-19 9:48 ` Gabriele Monaco
2025-08-21 12:18 ` Nam Cao
1 sibling, 1 reply; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 9:18 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
Hi!
On 14/08/25 17:08, Gabriele Monaco wrote:
...
> +/*
> + * 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);
> + if (unlikely(!ha_mon->timer.base))
> + hrtimer_setup(&ha_mon->timer, ha_monitor_timer_callback,
> + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> +}
...
> +/*
> + * 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 inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
> + u64 expire)
> +{
> + int mode = HRTIMER_MODE_REL;
> + u64 passed = 0;
> +
> + if (env >= 0 && env < ENV_MAX_STORED) {
> + if (ha_monitor_env_invalid(ha_mon, env))
> + return;
> + passed = ha_get_env(ha_mon, env);
> + }
> + if (RV_MON_TYPE == RV_MON_PER_CPU)
> + mode |= HRTIMER_MODE_PINNED;
> + hrtimer_start(&ha_mon->timer, ns_to_ktime(expire - passed), mode);
> +}
Does this also need to be _HARD on RT for the monitor to work?
Also, my only concern with the usage of per-task timers is that
reprogramming add overhead, so I wonder if this gets noticeable when
running some kind of performance sensitive workload in production (as it
was reported for dl-server). Did you test such a case?
Thanks!
Juri
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-19 9:18 ` Juri Lelli
@ 2025-08-19 9:48 ` Gabriele Monaco
2025-08-19 10:08 ` Juri Lelli
0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 9:48 UTC (permalink / raw)
To: Juri Lelli
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
On Tue, 2025-08-19 at 11:18 +0200, Juri Lelli wrote:
> Hi!
>
> On 14/08/25 17:08, Gabriele Monaco wrote:
>
> ...
>
> > +/*
> > + * 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);
> > + if (unlikely(!ha_mon->timer.base))
> > + hrtimer_setup(&ha_mon->timer,
> > ha_monitor_timer_callback,
> > + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> > +}
>
> ...
>
> > +/*
> > + * 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 inline void ha_start_timer_ns(struct ha_monitor *ha_mon,
> > enum envs env,
> > + u64 expire)
> > +{
> > + int mode = HRTIMER_MODE_REL;
> > + u64 passed = 0;
> > +
> > + if (env >= 0 && env < ENV_MAX_STORED) {
> > + if (ha_monitor_env_invalid(ha_mon, env))
> > + return;
> > + passed = ha_get_env(ha_mon, env);
> > + }
> > + if (RV_MON_TYPE == RV_MON_PER_CPU)
> > + mode |= HRTIMER_MODE_PINNED;
> > + hrtimer_start(&ha_mon->timer, ns_to_ktime(expire -
> > passed), mode);
> > +}
>
> Also, my only concern with the usage of per-task timers is that
> reprogramming add overhead, so I wonder if this gets noticeable when
> running some kind of performance sensitive workload in production (as
> it was reported for dl-server). Did you test such a case?
That's a good point, I need to check the actual overhead..
One thing to note is that this timer is used only on state constraints,
one could write roughly the same monitor like this:
+------------------------------------------+
| enqueued |
+------------------------------------------+
|
| sched_switch_in;clk < threshold_jiffies
v
or like this:
+------------------------------------------+
| enqueued |
| clk < threshold_jiffies |
+------------------------------------------+
|
| sched_switch_in
v
the first won't fail as soon as the threshold passes, but will
eventually fail when the sched_switch_in event occurs. This won't use a
timer at all (well, mostly, some calls are still made to keep the code
general, I could improve that if it matters).
Depending on the monitor, the first option could be a lower overhead
yet valid alternative to the second, if it's guaranteed sched_switch_in
will eventually come and reaction latency isn't an issue.
> Does this also need to be _HARD on RT for the monitor to work?
That might be something we want configurable actually.. I assume the
more aggressive the timer is, the more overhead it will have on the
system.
Some monitors might be fine with a bit of latency.
For example in the deadline case, I believe, the monitor is not
supposed to fix anything, but merely report violations. So we don't
really care to react on time, but to react at all.
I'm going to assess the overhead and see how to offer some more
configurability.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-14 15:08 ` [RFC PATCH 14/17] sched: Add deadline tracepoints Gabriele Monaco
@ 2025-08-19 9:56 ` Juri Lelli
2025-08-19 10:12 ` Peter Zijlstra
0 siblings, 1 reply; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 9:56 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu, Ingo Molnar,
Peter Zijlstra, linux-trace-kernel, Nam Cao, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
Hi!
On 14/08/25 17:08, Gabriele Monaco wrote:
> 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 | 55 ++++++++++++++++++++++++++++++++++++
> kernel/sched/deadline.c | 8 ++++++
> 2 files changed, 63 insertions(+)
>
> diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
> index 7b2645b50e78..f34cc1dc4a13 100644
> --- a/include/trace/events/sched.h
> +++ b/include/trace/events/sched.h
> @@ -609,6 +609,45 @@ TRACE_EVENT(sched_pi_setprio,
> __entry->oldprio, __entry->newprio)
> );
>
> +/*
> +DECLARE_EVENT_CLASS(sched_dl_template,
> +
> + TP_PROTO(struct sched_dl_entity *dl),
> +
> + TP_ARGS(dl),
> +
> + TP_STRUCT__entry(
> + __field( struct task_struct *, tsk )
> + __string( comm, dl->dl_server ? "server" : container_of(dl, struct task_struct, dl)->comm )
> + __field( pid_t, pid )
> + __field( s64, runtime )
> + __field( u64, deadline )
> + __field( int, dl_yielded )
I wonder if, while we are at it, we want to print all the other fields
as well (they might turn out to be useful). That would be
.:: static (easier to retrieve with just a trace)
- dl_runtime
- dl_deadline
- dl_period
.:: behaviour (RECLAIM)
- flags
.:: state
- dl_ bool flags in addition to dl_yielded
> + ),
> +
> + TP_fast_assign(
> + __assign_str(comm);
> + __entry->pid = dl->dl_server ? -1 : container_of(dl, struct task_struct, dl)->pid;
> + __entry->runtime = dl->runtime;
> + __entry->deadline = dl->deadline;
> + __entry->dl_yielded = dl->dl_yielded;
> + ),
> +
> + TP_printk("comm=%s pid=%d runtime=%lld deadline=%lld yielded=%d",
^^^
llu ?
> + __get_str(comm), __entry->pid,
> + __entry->runtime, __entry->deadline,
> + __entry->dl_yielded)
> +);
...
> @@ -1482,6 +1486,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);
> dl_se->dl_throttled = 1;
I believe we also need to trace the dl_check_constrained_dl() throttle,
please take a look.
Also - we discussed this point a little already offline - but I still
wonder if we have to do anything special for dl-server defer. Those
entities are started as throttled until 0-lag, so maybe we should still
trace them explicitly as so?
In addition, since it's related, maybe we should do something about
sched_switch event, that is currently not aware of deadlines, runtimes,
etc.
Thanks,
Juri
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-19 9:48 ` Gabriele Monaco
@ 2025-08-19 10:08 ` Juri Lelli
2025-08-19 10:53 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 10:08 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
On 19/08/25 11:48, Gabriele Monaco wrote:
>
>
> On Tue, 2025-08-19 at 11:18 +0200, Juri Lelli wrote:
> > Hi!
> >
> > On 14/08/25 17:08, Gabriele Monaco wrote:
> >
> > ...
> >
> > > +/*
> > > + * 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);
> > > + if (unlikely(!ha_mon->timer.base))
> > > + hrtimer_setup(&ha_mon->timer,
> > > ha_monitor_timer_callback,
> > > + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> > > +}
> >
> > ...
> >
> > > +/*
> > > + * 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 inline void ha_start_timer_ns(struct ha_monitor *ha_mon,
> > > enum envs env,
> > > + u64 expire)
> > > +{
> > > + int mode = HRTIMER_MODE_REL;
> > > + u64 passed = 0;
> > > +
> > > + if (env >= 0 && env < ENV_MAX_STORED) {
> > > + if (ha_monitor_env_invalid(ha_mon, env))
> > > + return;
> > > + passed = ha_get_env(ha_mon, env);
> > > + }
> > > + if (RV_MON_TYPE == RV_MON_PER_CPU)
> > > + mode |= HRTIMER_MODE_PINNED;
> > > + hrtimer_start(&ha_mon->timer, ns_to_ktime(expire -
> > > passed), mode);
> > > +}
> >
> > Also, my only concern with the usage of per-task timers is that
> > reprogramming add overhead, so I wonder if this gets noticeable when
> > running some kind of performance sensitive workload in production (as
> > it was reported for dl-server). Did you test such a case?
>
> That's a good point, I need to check the actual overhead..
>
> One thing to note is that this timer is used only on state constraints,
> one could write roughly the same monitor like this:
>
> +------------------------------------------+
> | enqueued |
> +------------------------------------------+
> |
> | sched_switch_in;clk < threshold_jiffies
> v
>
> or like this:
>
> +------------------------------------------+
> | enqueued |
> | clk < threshold_jiffies |
> +------------------------------------------+
> |
> | sched_switch_in
> v
>
> the first won't fail as soon as the threshold passes, but will
> eventually fail when the sched_switch_in event occurs. This won't use a
> timer at all (well, mostly, some calls are still made to keep the code
> general, I could improve that if it matters).
>
> Depending on the monitor, the first option could be a lower overhead
> yet valid alternative to the second, if it's guaranteed sched_switch_in
> will eventually come and reaction latency isn't an issue.
Right, as in the first example you have in the docs. I was thinking it
would be cool to possibly replace the hung task monitor with this one,
but again we would need to check for overhead, as the definition that
does expect a switch_in to eventually happen wouldn't work in this case.
> > Does this also need to be _HARD on RT for the monitor to work?
>
> That might be something we want configurable actually.. I assume the
> more aggressive the timer is, the more overhead it will have on the
> system.
> Some monitors might be fine with a bit of latency.
It might not only be about latency, as if the callback timer is not
serviced in case of starvation (if it's not hard) then the monitor won't
probably react and we won't be able to rely on it.
> For example in the deadline case, I believe, the monitor is not
> supposed to fix anything, but merely report violations. So we don't
> really care to react on time, but to react at all.
>
> I'm going to assess the overhead and see how to offer some more
> configurability.
Thanks!
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 9:56 ` Juri Lelli
@ 2025-08-19 10:12 ` Peter Zijlstra
2025-08-19 10:34 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Peter Zijlstra @ 2025-08-19 10:12 UTC (permalink / raw)
To: Juri Lelli
Cc: Gabriele Monaco, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Ingo Molnar, linux-trace-kernel, Nam Cao, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Tue, Aug 19, 2025 at 11:56:57AM +0200, Juri Lelli wrote:
> Hi!
>
> On 14/08/25 17:08, Gabriele Monaco wrote:
> > 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 | 55 ++++++++++++++++++++++++++++++++++++
> > kernel/sched/deadline.c | 8 ++++++
> > 2 files changed, 63 insertions(+)
> >
> > diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
> > index 7b2645b50e78..f34cc1dc4a13 100644
> > --- a/include/trace/events/sched.h
> > +++ b/include/trace/events/sched.h
> > @@ -609,6 +609,45 @@ TRACE_EVENT(sched_pi_setprio,
> > __entry->oldprio, __entry->newprio)
> > );
> >
> > +/*
> > +DECLARE_EVENT_CLASS(sched_dl_template,
> > +
> > + TP_PROTO(struct sched_dl_entity *dl),
> > +
> > + TP_ARGS(dl),
> > +
> > + TP_STRUCT__entry(
> > + __field( struct task_struct *, tsk )
> > + __string( comm, dl->dl_server ? "server" : container_of(dl, struct task_struct, dl)->comm )
> > + __field( pid_t, pid )
> > + __field( s64, runtime )
> > + __field( u64, deadline )
> > + __field( int, dl_yielded )
>
> I wonder if, while we are at it, we want to print all the other fields
> as well (they might turn out to be useful). That would be
>
> .:: static (easier to retrieve with just a trace)
> - dl_runtime
> - dl_deadline
> - dl_period
>
> .:: behaviour (RECLAIM)
> - flags
>
> .:: state
> - dl_ bool flags in addition to dl_yielded
All these things are used as _tp(). That means they don't have trace
buffer entries ever, why fill out fields?
> > + ),
> > +
> > + TP_fast_assign(
> > + __assign_str(comm);
> > + __entry->pid = dl->dl_server ? -1 : container_of(dl, struct task_struct, dl)->pid;
> > + __entry->runtime = dl->runtime;
> > + __entry->deadline = dl->deadline;
> > + __entry->dl_yielded = dl->dl_yielded;
> > + ),
> > +
> > + TP_printk("comm=%s pid=%d runtime=%lld deadline=%lld yielded=%d",
> ^^^
> llu ?
>
> > + __get_str(comm), __entry->pid,
> > + __entry->runtime, __entry->deadline,
> > + __entry->dl_yielded)
> > +);
>
> ...
>
> > @@ -1482,6 +1486,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);
> > dl_se->dl_throttled = 1;
>
> I believe we also need to trace the dl_check_constrained_dl() throttle,
> please take a look.
>
> Also - we discussed this point a little already offline - but I still
> wonder if we have to do anything special for dl-server defer. Those
> entities are started as throttled until 0-lag, so maybe we should still
> trace them explicitly as so?
>
> In addition, since it's related, maybe we should do something about
> sched_switch event, that is currently not aware of deadlines, runtimes,
> etc.
As per the whole _tp() thing, you can attach to the actual
sched_switch tracepoint with a module and read whatever you want.
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 10:12 ` Peter Zijlstra
@ 2025-08-19 10:34 ` Gabriele Monaco
2025-08-19 14:02 ` Juri Lelli
0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 10:34 UTC (permalink / raw)
To: Peter Zijlstra, Juri Lelli
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu, Ingo Molnar,
linux-trace-kernel, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
On Tue, 2025-08-19 at 12:12 +0200, Peter Zijlstra wrote:
> On Tue, Aug 19, 2025 at 11:56:57AM +0200, Juri Lelli wrote:
> > Hi!
> >
> > On 14/08/25 17:08, Gabriele Monaco wrote:
> > > 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 | 55
> > > ++++++++++++++++++++++++++++++++++++
> > > kernel/sched/deadline.c | 8 ++++++
> > > 2 files changed, 63 insertions(+)
> > >
> > > diff --git a/include/trace/events/sched.h
> > > b/include/trace/events/sched.h
> > > index 7b2645b50e78..f34cc1dc4a13 100644
> > > --- a/include/trace/events/sched.h
> > > +++ b/include/trace/events/sched.h
> > > @@ -609,6 +609,45 @@ TRACE_EVENT(sched_pi_setprio,
> > > __entry->oldprio, __entry->newprio)
> > > );
> > >
> > > +/*
> > > +DECLARE_EVENT_CLASS(sched_dl_template,
> > > +
> > > + TP_PROTO(struct sched_dl_entity *dl),
> > > +
> > > + TP_ARGS(dl),
> > > +
> > > + TP_STRUCT__entry(
> > > + __field( struct task_struct
> > > *, tsk )
> > > + __string( comm, dl->dl_server ?
> > > "server" : container_of(dl, struct task_struct, dl)-
> > > >comm )
> > > + __field( pid_t, pid )
> > > + __field(
> > > s64, runtime )
> > > + __field( u64, deadline )
> > > + __field( int, dl_yielded )
> >
> > I wonder if, while we are at it, we want to print all the other
> > fields
> > as well (they might turn out to be useful). That would be
> >
> > .:: static (easier to retrieve with just a trace)
> > - dl_runtime
> > - dl_deadline
> > - dl_period
> >
> > .:: behaviour (RECLAIM)
> > - flags
> >
> > .:: state
> > - dl_ bool flags in addition to dl_yielded
>
> All these things are used as _tp(). That means they don't have trace
> buffer entries ever, why fill out fields?
>
Right, that is a relic of the way I put it initially, this whole thing
is commented out (which is indeed confusing and barely noticeable in
the patch).
The tracepoints are in fact not exported to the tracefs and do not use
the print format.
I should have removed this, the real ones are at the bottom of the
file.
>
> > > + ),
> > > +
> > > + TP_fast_assign(
> > > + __assign_str(comm);
> > > + __entry->pid = dl->dl_server ? -1 :
> > > container_of(dl, struct task_struct, dl)->pid;
> > > + __entry->runtime = dl->runtime;
> > > + __entry->deadline = dl->deadline;
> > > + __entry->dl_yielded = dl->dl_yielded;
> > > + ),
> > > +
> > > + TP_printk("comm=%s pid=%d runtime=%lld deadline=%lld
> > > yielded=%d",
> > ^^^
> > llu ?
> >
As above, this should all go away.
> > > + __get_str(comm), __entry->pid,
> > > + __entry->runtime, __entry->deadline,
> > > + __entry->dl_yielded)
> > > +);
> >
> > ...
> >
> > > @@ -1482,6 +1486,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);
> > > dl_se->dl_throttled = 1;
> >
> > I believe we also need to trace the dl_check_constrained_dl()
> > throttle, please take a look.
Probably yes, strangely I couldn't see failures without it, but it may
be down to my test setup. I'm going to have a look.
> > Also - we discussed this point a little already offline - but I
> > still wonder if we have to do anything special for dl-server defer.
> > Those entities are started as throttled until 0-lag, so maybe we
> > should still trace them explicitly as so?
The naming might need a bit of a consistency check here, but for the
monitor, the server is running, armed or preempted. Before the 0-lag,
it will never be running, so it will stay as armed (fair tasks running)
or preempted (rt tasks running).
armed and preempted have the _throttled version just to indicate an
explicit throttle event occurred.
We might want to start it in the armed_throttled if we are really
guaranteed not to see a throttle event, but I think that would
complicate the model considerably.
We could instead validate the 0-lag concept in a separate, server-
specific model.
Does this initial model feel particularly wrong for the server case?
> > In addition, since it's related, maybe we should do something about
> > sched_switch event, that is currently not aware of deadlines,
> > runtimes, etc.
I'm not sure I follow you here, what relation with switch and
runtime/deadline should we enforce?
We don't really force the switch to occur timely after throttling, is
that what you mean?
Or a switch must occur again timely after replenishing?
> As per the whole _tp() thing, you can attach to the actual
> sched_switch tracepoint with a module and read whatever you want.
Yeah I believe Juri referred to model constraints on the already
existing events rather than new tracepoints here.
Thanks both,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata
2025-08-19 8:53 ` Juri Lelli
2025-08-19 9:14 ` Juri Lelli
@ 2025-08-19 10:40 ` Gabriele Monaco
1 sibling, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 10:40 UTC (permalink / raw)
To: Juri Lelli
Cc: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
linux-doc, Nam Cao, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Tue, 2025-08-19 at 10:53 +0200, Juri Lelli wrote:
> Hi!
>
> On 14/08/25 17:08, Gabriele Monaco wrote:
> >
> > +
> > +Examples
> > +--------
>
> Maybe add subsection titles to better mark separation between
> different examples?
Sure, makes sense.
>
> > +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.
> > +
> > +As a sample timed automaton we can define 'stall' as:
>
> Maybe indicate this first one is a not properly correct example
> (correct version follows)?
Yeah I should definitely be clearer about it. As you've guessed, this
example is to show things can be done differently as a tradeoff with
responsiveness, I should make that explicitly.
Thanks for the comments,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata
2025-08-19 9:14 ` Juri Lelli
@ 2025-08-19 10:46 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 10:46 UTC (permalink / raw)
To: Juri Lelli
Cc: linux-kernel, Steven Rostedt, Jonathan Corbet, linux-trace-kernel,
linux-doc, Nam Cao, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Tue, 2025-08-19 at 11:14 +0200, Juri Lelli wrote:
> On 19/08/25 10:53, Juri Lelli wrote:
> > Hi!
> >
> > On 14/08/25 17:08, Gabriele Monaco wrote:
>
> ...
>
> > > + 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)
> > > + return res;
> > > + if (next_state == enqueued && res)
> > > + start_timer(clk, threshold);
> >
> > So, then the timer callback checks the invariant and possibly
> > reports failure?
>
> Ah, OK. The 'standard' ha_monitor_timer_callback just reports failure
> (react) in case the timer fires. Which makes sense as at that point
> the invariant is broken. Maybe add some wording to highlight this?
Yeah indeed. That isn't even the 'standard' one as it isn't explicitly
configurable, but yes, at the expiration the invariant is already false
(I currently don't support AND/OR conditions in there).
I should make all this process clear, especially that those state
constraints are the only ones arming a timer and leaving the state
after that expiration (if the callback didn't run) or going through the
expiration itself implies a failure.
Thanks for pointing it out!
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-19 10:08 ` Juri Lelli
@ 2025-08-19 10:53 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 10:53 UTC (permalink / raw)
To: Juri Lelli
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Nam Cao, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
On Tue, 2025-08-19 at 12:08 +0200, Juri Lelli wrote:
> On 19/08/25 11:48, Gabriele Monaco wrote:
> > That's a good point, I need to check the actual overhead..
> >
> > One thing to note is that this timer is used only on state
> > constraints,
> > one could write roughly the same monitor like this:
> >
> > +------------------------------------------+
> > | enqueued |
> > +------------------------------------------+
> > |
> > | sched_switch_in;clk < threshold_jiffies
> > v
> >
> > or like this:
> >
> > +------------------------------------------+
> > | enqueued |
> > | clk < threshold_jiffies |
> > +------------------------------------------+
> > |
> > | sched_switch_in
> > v
> >
> > the first won't fail as soon as the threshold passes, but will
> > eventually fail when the sched_switch_in event occurs. This won't
> > use a timer at all (well, mostly, some calls are still made to keep
> > the code general, I could improve that if it matters).
> >
> > Depending on the monitor, the first option could be a lower
> > overhead yet valid alternative to the second, if it's guaranteed
> > sched_switch_in will eventually come and reaction latency isn't an
> > issue.
>
> Right, as in the first example you have in the docs. I was thinking
> it would be cool to possibly replace the hung task monitor with this
> one, but again we would need to check for overhead, as the definition
> that does expect a switch_in to eventually happen wouldn't work in
> this case.
Yeah if the overhead is really high that might be an option. Although
the monitor might become a bit pointless then: if a task starves
forever, no error will be reported.
If that's a real issue, I might look at other options where to check
for constraints (the tick perhaps).
> > > Does this also need to be _HARD on RT for the monitor to work?
> >
> > That might be something we want configurable actually.. I assume
> > the more aggressive the timer is, the more overhead it will have on
> > the system.
> > Some monitors might be fine with a bit of latency.
>
> It might not only be about latency, as if the callback timer is not
> serviced in case of starvation (if it's not hard) then the monitor
> won't probably react and we won't be able to rely on it.
I think hit that in some conditions and changed the ha_cancel_timer()
to handle this case.
After leaving the state arming a timer, we always cancel it (to avoid
it expiring outside) at that time if it was expiring but the callback
didn't run, the monitor fails.
Again, if the monitor never leaves the state, we'd never report a
failure, but I'm not sure how common that is.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 10:34 ` Gabriele Monaco
@ 2025-08-19 14:02 ` Juri Lelli
2025-08-19 14:21 ` Gabriele Monaco
` (2 more replies)
0 siblings, 3 replies; 57+ messages in thread
From: Juri Lelli @ 2025-08-19 14:02 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Peter Zijlstra, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Ingo Molnar, linux-trace-kernel, Nam Cao, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On 19/08/25 12:34, Gabriele Monaco wrote:
>
>
> On Tue, 2025-08-19 at 12:12 +0200, Peter Zijlstra wrote:
> > On Tue, Aug 19, 2025 at 11:56:57AM +0200, Juri Lelli wrote:
> > > Hi!
> > >
> > > On 14/08/25 17:08, Gabriele Monaco wrote:
...
> > > > @@ -1482,6 +1486,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);
> > > > dl_se->dl_throttled = 1;
> > >
> > > I believe we also need to trace the dl_check_constrained_dl()
> > > throttle, please take a look.
>
> Probably yes, strangely I couldn't see failures without it, but it may
> be down to my test setup. I'm going to have a look.
Not sure if you tested with constrained (deadline != period) tasks.
> > > Also - we discussed this point a little already offline - but I
> > > still wonder if we have to do anything special for dl-server defer.
> > > Those entities are started as throttled until 0-lag, so maybe we
> > > should still trace them explicitly as so?
>
> The naming might need a bit of a consistency check here, but for the
> monitor, the server is running, armed or preempted. Before the 0-lag,
> it will never be running, so it will stay as armed (fair tasks running)
> or preempted (rt tasks running).
>
> armed and preempted have the _throttled version just to indicate an
> explicit throttle event occurred.
>
> We might want to start it in the armed_throttled if we are really
> guaranteed not to see a throttle event, but I think that would
> complicate the model considerably.
>
> We could instead validate the 0-lag concept in a separate, server-
> specific model.
>
> Does this initial model feel particularly wrong for the server case?
No it doesn't atm. :-) Thanks for the additional information.
> > > In addition, since it's related, maybe we should do something about
> > > sched_switch event, that is currently not aware of deadlines,
> > > runtimes, etc.
>
> I'm not sure I follow you here, what relation with switch and
> runtime/deadline should we enforce?
>
> We don't really force the switch to occur timely after throttling, is
> that what you mean?
> Or a switch must occur again timely after replenishing?
Hummm, yeah I was wondering if we need something along these lines, but
we can also maybe leave it for the future.
> > As per the whole _tp() thing, you can attach to the actual
> > sched_switch tracepoint with a module and read whatever you want.
>
> Yeah I believe Juri referred to model constraints on the already
> existing events rather than new tracepoints here.
Separately from this series, maybe we should put such a module/bpf thing
somewhere shared, so it's easier to use it when needed.
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 14:02 ` Juri Lelli
@ 2025-08-19 14:21 ` Gabriele Monaco
2025-08-19 14:38 ` Phil Auld
2025-09-02 14:55 ` Juri Lelli
2 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-19 14:21 UTC (permalink / raw)
To: Juri Lelli
Cc: Peter Zijlstra, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Ingo Molnar, linux-trace-kernel, Nam Cao, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Tue, 2025-08-19 at 16:02 +0200, Juri Lelli wrote:
> On 19/08/25 12:34, Gabriele Monaco wrote:
> >
> >
> > On Tue, 2025-08-19 at 12:12 +0200, Peter Zijlstra wrote:
> > > On Tue, Aug 19, 2025 at 11:56:57AM +0200, Juri Lelli wrote:
> > > > Hi!
> > > >
> > > > On 14/08/25 17:08, Gabriele Monaco wrote:
>
> ...
>
> > > > > @@ -1482,6 +1486,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);
> > > > > dl_se->dl_throttled = 1;
> > > >
> > > > I believe we also need to trace the dl_check_constrained_dl()
> > > > throttle, please take a look.
> >
> > Probably yes, strangely I couldn't see failures without it, but it
> > may
> > be down to my test setup. I'm going to have a look.
>
> Not sure if you tested with constrained (deadline != period) tasks.
Not much actually.. I should start.
> > > > Also - we discussed this point a little already offline - but I
> > > > still wonder if we have to do anything special for dl-server
> > > > defer.
> > > > Those entities are started as throttled until 0-lag, so maybe
> > > > we
> > > > should still trace them explicitly as so?
> >
> > The naming might need a bit of a consistency check here, but for
> > the
> > monitor, the server is running, armed or preempted. Before the 0-
> > lag,
> > it will never be running, so it will stay as armed (fair tasks
> > running)
> > or preempted (rt tasks running).
> >
> > armed and preempted have the _throttled version just to indicate an
> > explicit throttle event occurred.
> >
> > We might want to start it in the armed_throttled if we are really
> > guaranteed not to see a throttle event, but I think that would
> > complicate the model considerably.
> >
> > We could instead validate the 0-lag concept in a separate, server-
> > specific model.
> >
> > Does this initial model feel particularly wrong for the server
> > case?
>
> No it doesn't atm. :-) Thanks for the additional information.
Perfect, I guess I need to write this a bit more clearly in the model
description.
>
> > > > In addition, since it's related, maybe we should do something
> > > > about
> > > > sched_switch event, that is currently not aware of deadlines,
> > > > runtimes, etc.
> >
> > I'm not sure I follow you here, what relation with switch and
> > runtime/deadline should we enforce?
> >
> > We don't really force the switch to occur timely after throttling,
> > is
> > that what you mean?
> > Or a switch must occur again timely after replenishing?
>
> Hummm, yeah I was wondering if we need something along these lines,
> but we can also maybe leave it for the future.
I'll have a thought about this, perhaps it's as simple as adding a few
more constraints on the edges.
>
> > > As per the whole _tp() thing, you can attach to the actual
> > > sched_switch tracepoint with a module and read whatever you want.
> >
> > Yeah I believe Juri referred to model constraints on the already
> > existing events rather than new tracepoints here.
>
> Separately from this series, maybe we should put such a module/bpf
> thing somewhere shared, so it's easier to use it when needed.
You mean some module/bpf to print those tracepoints to the ftrace
buffer? Yeah that might help, but it might be ugly and tracepoint-
specific.
Also perf probe doesn't support (yet) this type of tracepoints, but
once it does, I guess it would do the job quite nicely.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 14:02 ` Juri Lelli
2025-08-19 14:21 ` Gabriele Monaco
@ 2025-08-19 14:38 ` Phil Auld
2025-08-20 5:20 ` Juri Lelli
2025-09-02 14:55 ` Juri Lelli
2 siblings, 1 reply; 57+ messages in thread
From: Phil Auld @ 2025-08-19 14:38 UTC (permalink / raw)
To: Juri Lelli
Cc: Gabriele Monaco, Peter Zijlstra, linux-kernel, Steven Rostedt,
Masami Hiramatsu, Ingo Molnar, linux-trace-kernel, Nam Cao,
Tomas Glozar, Juri Lelli, Clark Williams, John Kacur
Hi Juri,
On Tue, Aug 19, 2025 at 04:02:04PM +0200 Juri Lelli wrote:
> On 19/08/25 12:34, Gabriele Monaco wrote:
> >
> >
> > On Tue, 2025-08-19 at 12:12 +0200, Peter Zijlstra wrote:
> > > On Tue, Aug 19, 2025 at 11:56:57AM +0200, Juri Lelli wrote:
> > > > Hi!
> > > >
> > > > On 14/08/25 17:08, Gabriele Monaco wrote:
>
> ...
>
> > > > > @@ -1482,6 +1486,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);
> > > > > dl_se->dl_throttled = 1;
> > > >
> > > > I believe we also need to trace the dl_check_constrained_dl()
> > > > throttle, please take a look.
> >
> > Probably yes, strangely I couldn't see failures without it, but it may
> > be down to my test setup. I'm going to have a look.
>
> Not sure if you tested with constrained (deadline != period) tasks.
>
> > > > Also - we discussed this point a little already offline - but I
> > > > still wonder if we have to do anything special for dl-server defer.
> > > > Those entities are started as throttled until 0-lag, so maybe we
> > > > should still trace them explicitly as so?
> >
> > The naming might need a bit of a consistency check here, but for the
> > monitor, the server is running, armed or preempted. Before the 0-lag,
> > it will never be running, so it will stay as armed (fair tasks running)
> > or preempted (rt tasks running).
> >
> > armed and preempted have the _throttled version just to indicate an
> > explicit throttle event occurred.
> >
> > We might want to start it in the armed_throttled if we are really
> > guaranteed not to see a throttle event, but I think that would
> > complicate the model considerably.
> >
> > We could instead validate the 0-lag concept in a separate, server-
> > specific model.
> >
> > Does this initial model feel particularly wrong for the server case?
>
> No it doesn't atm. :-) Thanks for the additional information.
>
> > > > In addition, since it's related, maybe we should do something about
> > > > sched_switch event, that is currently not aware of deadlines,
> > > > runtimes, etc.
> >
> > I'm not sure I follow you here, what relation with switch and
> > runtime/deadline should we enforce?
> >
> > We don't really force the switch to occur timely after throttling, is
> > that what you mean?
> > Or a switch must occur again timely after replenishing?
>
> Hummm, yeah I was wondering if we need something along these lines, but
> we can also maybe leave it for the future.
>
> > > As per the whole _tp() thing, you can attach to the actual
> > > sched_switch tracepoint with a module and read whatever you want.
> >
> > Yeah I believe Juri referred to model constraints on the already
> > existing events rather than new tracepoints here.
>
> Separately from this series, maybe we should put such a module/bpf thing
> somewhere shared, so it's easier to use it when needed.
>
>
A few of us use: https://github.com/qais-yousef/sched_tp.git
This has all the current scheduler raw tps exposed, I believe, but would
need updates for these new ones, of course.
I have a gitlab fork with our perf team uses to get at the ones they use
(mostly the nr_running ones to make their heat maps).
Fwiw...
Cheers,
Phil
--
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 14:38 ` Phil Auld
@ 2025-08-20 5:20 ` Juri Lelli
0 siblings, 0 replies; 57+ messages in thread
From: Juri Lelli @ 2025-08-20 5:20 UTC (permalink / raw)
To: Phil Auld
Cc: Gabriele Monaco, Peter Zijlstra, linux-kernel, Steven Rostedt,
Masami Hiramatsu, Ingo Molnar, linux-trace-kernel, Nam Cao,
Tomas Glozar, Juri Lelli, Clark Williams, John Kacur
On 19/08/25 10:38, Phil Auld wrote:
...
> A few of us use: https://github.com/qais-yousef/sched_tp.git
>
> This has all the current scheduler raw tps exposed, I believe, but would
> need updates for these new ones, of course.
>
> I have a gitlab fork with our perf team uses to get at the ones they use
> (mostly the nr_running ones to make their heat maps).
Ah, cool. Didn't know about this, thanks for sharing! I'll take a look.
Best,
Juri
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros
2025-08-14 15:07 ` [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros Gabriele Monaco
@ 2025-08-21 8:14 ` Nam Cao
2025-08-21 8:49 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 8:14 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, Aug 14, 2025 at 05:07:53PM +0200, Gabriele Monaco wrote:
> 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>
Yes please! The macros are not pleasant to work with.
> ---
> include/linux/rv.h | 4 +
> include/rv/automata.h | 132 ++--
> include/rv/da_monitor.h | 912 ++++++++++++-------------
> kernel/trace/rv/monitors/nrp/nrp.c | 22 +-
> kernel/trace/rv/monitors/nrp/nrp.h | 2 +
> kernel/trace/rv/monitors/opid/opid.c | 32 +-
> kernel/trace/rv/monitors/opid/opid.h | 2 +
> kernel/trace/rv/monitors/sco/sco.c | 18 +-
> kernel/trace/rv/monitors/sco/sco.h | 2 +
> kernel/trace/rv/monitors/scpd/scpd.c | 20 +-
> kernel/trace/rv/monitors/scpd/scpd.h | 2 +
> kernel/trace/rv/monitors/snep/snep.c | 20 +-
> kernel/trace/rv/monitors/snep/snep.h | 2 +
> kernel/trace/rv/monitors/snroc/snroc.c | 18 +-
> kernel/trace/rv/monitors/snroc/snroc.h | 2 +
> kernel/trace/rv/monitors/sssw/sssw.c | 30 +-
> kernel/trace/rv/monitors/sssw/sssw.h | 2 +
> kernel/trace/rv/monitors/sts/sts.c | 26 +-
> kernel/trace/rv/monitors/sts/sts.h | 2 +
> kernel/trace/rv/monitors/wip/wip.c | 18 +-
> kernel/trace/rv/monitors/wip/wip.h | 2 +
> kernel/trace/rv/monitors/wwnr/wwnr.c | 20 +-
> kernel/trace/rv/monitors/wwnr/wwnr.h | 2 +
> 23 files changed, 650 insertions(+), 642 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)
I think these macros make it harder to read the code. E.g. it is not
obvious what is "events" in "enum events event".
How about renaming these to be the same for all monitors, and get rid of
these 4 macros?
Something like
enum states_wip -> enum da_states
state_max_wip -> da_state_max
etc
Just my preference, of course. You probably have your reasons for doing it
this way?
> +/*
> + * 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";
Just notice that this probably should be
if (BUG_ON((state < 0) || (state >= STATE_MAX)))
You shouldn't do it in this patch of course. I just want to note it down.
> index 17fa4f6e5ea6..bc02334aa8be 100644
> --- a/include/rv/da_monitor.h
> +++ b/include/rv/da_monitor.h
> @@ -13,97 +13,102 @@
>
> #include <rv/automata.h>
> #include <linux/rv.h>
> +#include <linux/stringify.h>
> #include <linux/bug.h>
> #include <linux/sched.h>
>
> +#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
> +#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
These macros as well. Should we rename the monitors to be the same and get
rid of the macros?
I see you took this RV_MONITOR_NAME idea from LTL. But I'm starting to
wonder if this is really a good idea.
What do you think?
> +#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;
> +}
You have two da_event(), which is mostly similar, except the function's
signature and the trace event. Would it be sane to unify them, and only
putting the differences in #ifdef?
Perhaps something like:
#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
typedef struct {} monitor_target;
static void da_trace_event(struct da_monitor *da_mon, monitor_target target,
enum events event, enum states curr, enum states next)
{
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));
}
#elif RV_MON_TYPE == RV_MON_PER_TASK
typedef struct task_struct *monitor_target;
static void da_trace_event(struct da_monitor *da_mon, struct task_struct *task,
enum events event, enum states curr, enum states next)
{
CONCATENATE(trace_event_, MONITOR_NAME)(task->pid,
model_get_state_name(curr_state),
model_get_event_name(event),
model_get_state_name(next_state),
model_is_final_state(next_state));
}
#endif
static inline bool
da_event(struct da_monitor *da_mon, monitor_target target, 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);
da_trace_event(da_mon, event, target, curr_state, next_state);
return false;
}
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
da_trace_error(...);
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;
}
Same for the other functions.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types
2025-08-14 15:07 ` [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types Gabriele Monaco
@ 2025-08-21 8:29 ` Nam Cao
2025-08-21 8:51 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 8:29 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:07:57PM +0200, Gabriele Monaco wrote:
> 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.
Nice, Python is sometimes hard to read because the type is not clear.
> - def __get_event_variables(self):
> + def __get_event_variables(self) -> tuple[list[str], list[str]]:
This annotation is wrong. It becomes correct only after your later patch
(verification/rvgen: Add support for Hybrid Automata)
The rest looks good.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup
2025-08-14 15:07 ` [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
@ 2025-08-21 8:32 ` Nam Cao
0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-08-21 8:32 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:07:58PM +0200, Gabriele Monaco wrote:
> 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.
>
> 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: [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers
2025-08-14 15:07 ` [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
@ 2025-08-21 8:33 ` Nam Cao
0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-08-21 8:33 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, Aug 14, 2025 at 05:07:59PM +0200, Gabriele Monaco wrote:
> 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.
>
> 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: [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros
2025-08-21 8:14 ` Nam Cao
@ 2025-08-21 8:49 ` Gabriele Monaco
2025-08-25 8:02 ` Gabriele Monaco
2025-08-25 8:03 ` Nam Cao
0 siblings, 2 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-21 8:49 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, 2025-08-21 at 10:14 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:07:53PM +0200, Gabriele Monaco wrote:
> > 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.
...
> > +#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)
>
> I think these macros make it harder to read the code. E.g. it is not
> obvious what is "events" in "enum events event".
>
> How about renaming these to be the same for all monitors, and get rid
> of these 4 macros?
>
> Something like
>
> enum states_wip -> enum da_states
> state_max_wip -> da_state_max
> etc
>
> Just my preference, of course. You probably have your reasons for
> doing it this way?
I think the reason was mostly laziness / wish to change less things.
This would require to change a bit more in each monitor's header and
rvgen, but since I'm already changing those, it should be no problem.
I assume the compiler would be just fine with the change and I don't see
a use-case where one would grab multiple definitions of those enums to
get a clash.
Good point, I'll look into that.
> > +/*
> > + * 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";
>
> Just notice that this probably should be
> if (BUG_ON((state < 0) || (state >= STATE_MAX)))
>
> You shouldn't do it in this patch of course. I just want to note it
> down.
Mmh, I'm not quite sure about this one, sure it's not a good thing when
we try to get an invalid state/event, but isn't BUG a bit too much here?
We're handling things gracefully so the kernel is not broken (although
rv likely is).
If you really think we should note that, what about just WARN/WARN_ONCE ?
> > index 17fa4f6e5ea6..bc02334aa8be 100644
> > --- a/include/rv/da_monitor.h
> > +++ b/include/rv/da_monitor.h
> > @@ -13,97 +13,102 @@
> >
> > #include <rv/automata.h>
> > #include <linux/rv.h>
> > +#include <linux/stringify.h>
> > #include <linux/bug.h>
> > #include <linux/sched.h>
> >
> > +#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
> > +#define RV_DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
>
> These macros as well. Should we rename the monitors to be the same
> and get rid of the macros?
>
> I see you took this RV_MONITOR_NAME idea from LTL. But I'm starting
> to wonder if this is really a good idea.
>
> What do you think?
Same laziness, I'll look into that!
They are static so they won't ever be defined together, nor I see a
reason for them not to be static.
One thing to note is that by using the same names everywhere, the
symbols won't be as accessible from debug tools (gdb/BPF or whatever),
I'm not sure that's really an issue, but it's the only downside coming
to my mind.
> > +#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;
> > +}
>
> You have two da_event(), which is mostly similar, except the
> function's signature and the trace event. Would it be sane to unify
> them, and only putting the differences in #ifdef?
>
> Perhaps something like:
>
> #if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
>
> typedef struct {} monitor_target;
>
> static void da_trace_event(struct da_monitor *da_mon, monitor_target
> target,
> enum events event, enum states curr, enum
> states next)
> {
> 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));
> }
>
> #elif RV_MON_TYPE == RV_MON_PER_TASK
>
> typedef struct task_struct *monitor_target;
>
> static void da_trace_event(struct da_monitor *da_mon, struct
> task_struct *task,
> enum events event, enum states curr, enum
> states next)
> {
> CONCATENATE(trace_event_, MONITOR_NAME)(task->pid,
> model_get_state_name(curr_state),
> model_get_event_name(event),
> model_get_state_name(next_state),
> model_is_final_state(next_state));
> }
>
> #endif
>
> static inline bool
> da_event(struct da_monitor *da_mon, monitor_target target, 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);
> da_trace_event(da_mon, event, target,
> curr_state, next_state);
> return false;
> }
> if (likely(try_cmpxchg(&da_mon->curr_state,
> &curr_state, next_state))) {
> da_trace_error(...);
> 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;
> }
>
> Same for the other functions.
Mmh, that's been bugging me throughout every change, but I'm not sure
it's that simple, implicit monitors don't have a target at all, so all
function prototypes are different because that needs to propagate.
Now that I think about it, it may not be a big deal not to pass the
target at all and retrieve it from the da_mon (that's just pointer
arithmetic the compiler might even optimise out).
I'm not sure that would be as simple if per-cpu monitors stopped being
implicit (like they are in your patch: they do have a target), but that
may never happen.
I'll give it a shot, thanks for pointing that out!
Thanks again,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types
2025-08-21 8:29 ` Nam Cao
@ 2025-08-21 8:51 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-21 8:51 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, 2025-08-21 at 10:29 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:07:57PM +0200, Gabriele Monaco wrote:
> > 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.
>
> Nice, Python is sometimes hard to read because the type is not clear.
>
> > - def __get_event_variables(self):
> > + def __get_event_variables(self) -> tuple[list[str],
> > list[str]]:
>
> This annotation is wrong. It becomes correct only after your later
> patch
> (verification/rvgen: Add support for Hybrid Automata)
Mmh, good point, that happened after I moved all cleanup patches before
the main changes (this was done after). I'm going to fix that.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-14 15:08 ` [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type Gabriele Monaco
2025-08-19 9:18 ` Juri Lelli
@ 2025-08-21 12:18 ` Nam Cao
2025-08-25 7:48 ` Gabriele Monaco
1 sibling, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 12:18 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, Aug 14, 2025 at 05:08:00PM +0200, Gabriele Monaco wrote:
> 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>
So you have figured out how to deal with the time problem. Cool!
> ---
> include/linux/rv.h | 26 +++
> include/rv/da_monitor.h | 45 ++++-
> include/rv/ha_monitor.h | 384 +++++++++++++++++++++++++++++++++++++
> kernel/trace/rv/Kconfig | 13 ++
> kernel/trace/rv/rv_trace.h | 63 ++++++
> 5 files changed, 526 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..6a7594080db1 100644
> --- a/include/linux/rv.h
> +++ b/include/linux/rv.h
> @@ -83,11 +83,37 @@ 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
> +
> +/*
> + * Hybrid automaton per-object variables.
> + */
> +struct ha_monitor {
> + struct da_monitor da_mon;
> + u64 env_store[MAX_HA_ENV_LEN];
> + struct hrtimer timer;
> +};
> +#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
> +
> +#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;
> };
I'm curious, instead of a new monitor type, would the entire thing be
simpler if these new features are added as extension to DA monitor instead?
The existing "pure DA" monitors would just not use the constraint and timer
stuffs and would behave same as before.
Just an idea, I'm not sure how it would look like. But I think we might
reduce some line count.
> +/*
> + * 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++)
> + smp_store_mb(ha_mon->env_store[i], ENV_INVALID_VALUE);
Why is memory barrier needed here?
I think checkpatch.pl should complain about this? Please add a comment
explaining the purpose of this memory barrier.
The same applied for the other memory barriers.
> +}
> +
> +/*
> + * 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);
> + if (unlikely(!ha_mon->timer.base))
> + hrtimer_setup(&ha_mon->timer, ha_monitor_timer_callback,
> + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> +}
> +
> +/*
> + * 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(ha_mon->timer.base))
> + ha_cancel_timer(ha_mon);
> +}
Looking at hrtimer->timer.base seems quite hacky. It seems that this could
be broken due to a future hrtimer's refactoring.
Instead, how about moving hrtimer_setup() into monitor's enabling function?
Then you can always hrtimer_cancel() here.
> +/*
> + * ha_cancel_timer - Cancel the timer and return whether it expired
> + *
> + * Return true if the timer was cancelled after expiration but before the
> + * callback could run.
> + */
> +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
> +{
> + ktime_t remaining;
> +
> + if (!hrtimer_active(&ha_mon->timer))
> + return false;
> + remaining = hrtimer_get_remaining(&ha_mon->timer);
> + if (hrtimer_try_to_cancel(&ha_mon->timer) == 1 && remaining < 0)
> + return true;
> + return false;
> +}
This is still racy. The timer could expire after hrtimer_get_remaining(),
but before hrtimer_try_to_cancel()
Is it really important that we need to care about the tiny window "after
expiration but before the callback could run"? What if we just use
hrtimer_cancel() here?
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings
2025-08-14 15:08 ` [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
@ 2025-08-21 12:22 ` Nam Cao
2025-08-21 13:22 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 12:22 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:08:01PM +0200, Gabriele Monaco wrote:
> 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.
The current parser does have a few problems. Not all valid .dot files are
accepted.
I have a patch buried somewhere which removes the custom parser and instead
uses a library. But then it adds a new dependency, so I'm not sure if it is
worth..
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
@ 2025-08-21 12:38 ` Nam Cao
2025-08-21 13:15 ` Gabriele Monaco
2025-08-25 9:55 ` Nam Cao
2025-08-25 10:06 ` Nam Cao
2 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 12:38 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> +import re
Oh no..
> +from typing import Iterator
>
> 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
^
My regex knowledge is not that great, but I think this is not
standard regex syntax, right?
I'm guessing this is Python's feature to store this group into
"env"?
I think my brain's battery just run out, I will continue with the review
another day.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-21 12:38 ` Nam Cao
@ 2025-08-21 13:15 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-21 13:15 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, 2025-08-21 at 14:38 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> > +import re
>
> Oh no..
I was tempted to suggest rewriting the dot parser with regex but I
guess I'll hold that back!
>
> > +from typing import Iterator
> >
> > 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
> ^
> My regex knowledge is not that great, but I think this is
> not
> standard regex syntax, right?
>
> I'm guessing this is Python's feature to store this group
> into
> "env"?
>
> I think my brain's battery just run out, I will continue with the
> review another day.
Yeah, you got that right. That P is kinda python specific, but it seems
other regex variants (perl/PCRE) work with the same notation, there's
another notation supported by browsers and .NET without the P.
Unfortunately regex are as standard as many other things in the unix
world [1]..
I tried to be as verbose as possible in this regex and I think a
crafted parser would look much uglier, but I'd appreciate your
comments!
Thanks,
Gabriele
[1] - https://xkcd.com/927/
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings
2025-08-21 12:22 ` Nam Cao
@ 2025-08-21 13:22 ` Gabriele Monaco
2025-08-21 15:15 ` Nam Cao
0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-21 13:22 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, 2025-08-21 at 14:22 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:08:01PM +0200, Gabriele Monaco wrote:
> > 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.
>
> The current parser does have a few problems. Not all valid .dot files
> are accepted.
>
> I have a patch buried somewhere which removes the custom parser and
> instead uses a library. But then it adds a new dependency, so I'm not
> sure if it is worth..
Yeah it isn't really robust, I tried to improve it a bit but sure
something is still failing it.
We don't need full dot capabilities, but just extract some keywords,
I'd avoid pulling in a dependency for that.
I'm imagining users would either generate graphs from the
Waters/Supremica tool [1] or copy/edit existing ones, so I'm not sure
they can go that far.
Still that's hacky because some things are just lightly implied by the
code (e.g. initial/final states, edges labels, etc.), so one day we
should at the very least say what DOT is valid and what not.
Do you have specific examples of what doesn't work?
Thanks,
Gabriele
[1] - https://github.com/robimalik/Waters/releases
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings
2025-08-21 13:22 ` Gabriele Monaco
@ 2025-08-21 15:15 ` Nam Cao
2025-08-21 15:58 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-21 15:15 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 21, 2025 at 03:22:23PM +0200, Gabriele Monaco wrote:
> On Thu, 2025-08-21 at 14:22 +0200, Nam Cao wrote:
> > On Thu, Aug 14, 2025 at 05:08:01PM +0200, Gabriele Monaco wrote:
> > > 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.
> >
> > The current parser does have a few problems. Not all valid .dot files
> > are accepted.
> >
> > I have a patch buried somewhere which removes the custom parser and
> > instead uses a library. But then it adds a new dependency, so I'm not
> > sure if it is worth..
>
> Yeah it isn't really robust, I tried to improve it a bit but sure
> something is still failing it.
> We don't need full dot capabilities, but just extract some keywords,
> I'd avoid pulling in a dependency for that.
>
> I'm imagining users would either generate graphs from the
> Waters/Supremica tool [1] or copy/edit existing ones, so I'm not sure
> they can go that far.
When I tried out the DA monitor, I edited the .dot files directly.
> Still that's hacky because some things are just lightly implied by the
> code (e.g. initial/final states, edges labels, etc.), so one day we
> should at the very least say what DOT is valid and what not.
We could also rewrite the parser using ply with a well-defined grammar and
tokenizer, like how the LTL parser is implemented. Doing it this way would
be easier to validate as well, because the grammar would be mostly
copy-pasted from the specification.
> Do you have specific examples of what doesn't work?
Two things that I can remember:
- breaking long lines
- C-style and C++-style comments
Not really relevant if you do not edit the .dot files directly like I did.
But these things are valid according to https://graphviz.org/doc/info/lang.html
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings
2025-08-21 15:15 ` Nam Cao
@ 2025-08-21 15:58 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-21 15:58 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
2025-08-21T15:15:36Z Nam Cao <namcao@linutronix.de>:
> We could also rewrite the parser using ply with a well-defined grammar and
> tokenizer, like how the LTL parser is implemented. Doing it this way would
> be easier to validate as well, because the grammar would be mostly
> copy-pasted from the specification.
Good idea, I can look into that and see how hard it's going to be (I'm not familiar with ply, but I see it's well documented).
>> Do you have specific examples of what doesn't work?
>
> Two things that I can remember:
>
> - breaking long lines
>
> - C-style and C++-style comments
>
Yeah those could break some assumptions (and they shouldn't), especially the line breaks.. And supporting it in the current parser may make it just uglier..
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-21 12:18 ` Nam Cao
@ 2025-08-25 7:48 ` Gabriele Monaco
2025-08-25 8:13 ` Nam Cao
0 siblings, 1 reply; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-25 7:48 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, 2025-08-21 at 14:18 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:08:00PM +0200, Gabriele Monaco wrote:
> > 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>
>
> So you have figured out how to deal with the time problem. Cool!
>
> I'm curious, instead of a new monitor type, would the entire thing be
> simpler if these new features are added as extension to DA monitor
> instead?
>
> The existing "pure DA" monitors would just not use the constraint and
> timer stuffs and would behave same as before.
>
> Just an idea, I'm not sure how it would look like. But I think we
> might reduce some line count.
Mmh, that might save some lines, especially the *_hooks() macros.
The few functions that are now duplicated would end up together with a
condition, though.
I'm however not too fond of forcing any DA user to allocate space for a
timer. Imagine a custom kernel for an embedded device trying to squeeze
some RV monitors in tasks and ending up requiring 64 bytes per monitor
instead of 8.
If this doesn't look confusing to you, I'd prefer them separate at
least there.
> > +/*
> > + * 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++)
> > + smp_store_mb(ha_mon->env_store[i],
> > ENV_INVALID_VALUE);
>
> Why is memory barrier needed here?
Right, those are not needed, will remove.
> I think checkpatch.pl should complain about this? Please add a
> comment explaining the purpose of this memory barrier.
>
> The same applied for the other memory barriers.
>
> > +}
> > +
> > +/*
> > + * 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);
> > + if (unlikely(!ha_mon->timer.base))
> > + hrtimer_setup(&ha_mon->timer,
> > ha_monitor_timer_callback,
> > + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> > +}
> > +
> > +/*
> > + * 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(ha_mon->timer.base))
> > + ha_cancel_timer(ha_mon);
> > +}
>
> Looking at hrtimer->timer.base seems quite hacky. It seems that this
> could be broken due to a future hrtimer's refactoring.
>
> Instead, how about moving hrtimer_setup() into monitor's enabling
> function?
> Then you can always hrtimer_cancel() here.
I didn't really like that either, it just seemed the easiest option at
the time. But yours is a good point..
Timers are per monitor and the enabling function is to register the
monitor functionality, so it won't run for all tasks (and some aren't
there yet).
The right spot for this is the start function (where it's currently
happening), the main issue is that resets are running at times when the
monitor is not started, so the timer may not be there when we attempt
to cancel it.
I'll check a bit more if we really need those resets, and in the worst
case use something like da_monitoring() as a condition there.
Then I'd remove the same condition in ha_monitor_init_env(), which
basically would setup the timer every time the monitor starts, that
doesn't seem an issue to me.
>
> > +/*
> > + * ha_cancel_timer - Cancel the timer and return whether it
> > expired
> > + *
> > + * Return true if the timer was cancelled after expiration but
> > before the
> > + * callback could run.
> > + */
> > +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
> > +{
> > + ktime_t remaining;
> > +
> > + if (!hrtimer_active(&ha_mon->timer))
> > + return false;
> > + remaining = hrtimer_get_remaining(&ha_mon->timer);
> > + if (hrtimer_try_to_cancel(&ha_mon->timer) == 1 &&
> > remaining < 0)
> > + return true;
> > + return false;
> > +}
>
> This is still racy. The timer could expire after
> hrtimer_get_remaining(), but before hrtimer_try_to_cancel()
I believe in that case hrtimer_try_to_cancel would return -1 (callback
is executing) or 0 if the callback is done.
I guess that should be serialised by the hrtimers locks, but I'll
double check if there can indeed be a race.
> Is it really important that we need to care about the tiny window
> "after expiration but before the callback could run"? What if we just
> use hrtimer_cancel() here?
As Juri pointed out, on PREEMPT_RT kernels if the timer isn't marked as
_HARD, the window won't even be tiny.
That was a (blind) attempt to reduce the overhead of using timers, I'm
going to run more tests to understand if that really makes sense. Not
using _HARD and having the timer not serviced, would in fact end up
just like not using timers at all, which is what the monitor could do
in the first place.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros
2025-08-21 8:49 ` Gabriele Monaco
@ 2025-08-25 8:02 ` Gabriele Monaco
2025-08-25 8:03 ` Nam Cao
1 sibling, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-25 8:02 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, 2025-08-21 at 10:49 +0200, Gabriele Monaco wrote:
> On Thu, 2025-08-21 at 10:14 +0200, Nam Cao wrote:
>
> > > +#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)
> >
> > I think these macros make it harder to read the code. E.g. it is
> > not obvious what is "events" in "enum events event".
> >
> > How about renaming these to be the same for all monitors, and get
> > rid of these 4 macros?
> >
> > Something like
> >
> > enum states_wip -> enum da_states
> > state_max_wip -> da_state_max
> > etc
> >
> > Just my preference, of course. You probably have your reasons for
> > doing it this way?
I had more thoughts on this and I guess at least these enums might need
to stay as they are.
I'm working on a patch to write some sort of unit test for monitors,
where I inject events and see that the errors are triggered.
For readability that requires the events definitions and, currently,
I'm putting all those tests in the same file. It looks simpler to keep
the enums different.
But of course any suggestion is welcome.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros
2025-08-21 8:49 ` Gabriele Monaco
2025-08-25 8:02 ` Gabriele Monaco
@ 2025-08-25 8:03 ` Nam Cao
1 sibling, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-08-25 8:03 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Thu, Aug 21, 2025 at 10:49:05AM +0200, Gabriele Monaco wrote:
> On Thu, 2025-08-21 at 10:14 +0200, Nam Cao wrote:
> > On Thu, Aug 14, 2025 at 05:07:53PM +0200, Gabriele Monaco wrote:
> I think the reason was mostly laziness / wish to change less things.
Laziness is contagious, maybe you got it from me.
> > > +/*
> > > + * 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";
> >
> > Just notice that this probably should be
> > if (BUG_ON((state < 0) || (state >= STATE_MAX)))
> >
> > You shouldn't do it in this patch of course. I just want to note it
> > down.
>
> Mmh, I'm not quite sure about this one, sure it's not a good thing when
> we try to get an invalid state/event, but isn't BUG a bit too much here?
> We're handling things gracefully so the kernel is not broken (although
> rv likely is).
>
> If you really think we should note that, what about just WARN/WARN_ONCE ?
I think that if RV is run, then the system is just being tested, and
therefore it is not a big problem if the kernel crashes.
But WARN_ONCE is fine too.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-25 7:48 ` Gabriele Monaco
@ 2025-08-25 8:13 ` Nam Cao
2025-08-25 8:32 ` Gabriele Monaco
0 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-25 8:13 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Mon, Aug 25, 2025 at 09:48:23AM +0200, Gabriele Monaco wrote:
> On Thu, 2025-08-21 at 14:18 +0200, Nam Cao wrote:
> > On Thu, Aug 14, 2025 at 05:08:00PM +0200, Gabriele Monaco wrote:
> > > 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>
> >
> > So you have figured out how to deal with the time problem. Cool!
> >
> > I'm curious, instead of a new monitor type, would the entire thing be
> > simpler if these new features are added as extension to DA monitor
> > instead?
> >
> > The existing "pure DA" monitors would just not use the constraint and
> > timer stuffs and would behave same as before.
> >
> > Just an idea, I'm not sure how it would look like. But I think we
> > might reduce some line count.
>
> Mmh, that might save some lines, especially the *_hooks() macros.
> The few functions that are now duplicated would end up together with a
> condition, though.
>
> I'm however not too fond of forcing any DA user to allocate space for a
> timer. Imagine a custom kernel for an embedded device trying to squeeze
> some RV monitors in tasks and ending up requiring 64 bytes per monitor
> instead of 8.
I'm not sure if I follow. We put "union rv_task_monitor" in task_struct, so
we always require 64 bytes, regardless of the monitor type?
> If this doesn't look confusing to you, I'd prefer them separate at
> least there.
The current implementation is fine. It is just an thought that I think may
be worth considering. But I trust you know best what to do here.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type
2025-08-25 8:13 ` Nam Cao
@ 2025-08-25 8:32 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-25 8:32 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, Masami Hiramatsu,
linux-trace-kernel, Tomas Glozar, Juri Lelli, Clark Williams,
John Kacur
On Mon, 2025-08-25 at 10:13 +0200, Nam Cao wrote:
> On Mon, Aug 25, 2025 at 09:48:23AM +0200, Gabriele Monaco wrote:
> > On Thu, 2025-08-21 at 14:18 +0200, Nam Cao wrote:
> > > On Thu, Aug 14, 2025 at 05:08:00PM +0200, Gabriele Monaco wrote:
> > > > 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>
> > >
> > > So you have figured out how to deal with the time problem. Cool!
> > >
> > > I'm curious, instead of a new monitor type, would the entire
> > > thing be
> > > simpler if these new features are added as extension to DA
> > > monitor
> > > instead?
> > >
> > > The existing "pure DA" monitors would just not use the constraint
> > > and
> > > timer stuffs and would behave same as before.
> > >
> > > Just an idea, I'm not sure how it would look like. But I think we
> > > might reduce some line count.
> >
> > Mmh, that might save some lines, especially the *_hooks() macros.
> > The few functions that are now duplicated would end up together
> > with a
> > condition, though.
> >
> > I'm however not too fond of forcing any DA user to allocate space
> > for a
> > timer. Imagine a custom kernel for an embedded device trying to
> > squeeze
> > some RV monitors in tasks and ending up requiring 64 bytes per
> > monitor
> > instead of 8.
>
> I'm not sure if I follow. We put "union rv_task_monitor" in
> task_struct, so
> we always require 64 bytes, regardless of the monitor type?
That's right, but if no HA monitor is compiled in, struct ha_monitor is
empty, so union rv_task_monitor is as large as DA/LTL.
> #ifdef CONFIG_RV_HA_MONITOR
>
> struct ha_monitor {
> struct da_monitor da_mon;
> u64 env_store[MAX_HA_ENV_LEN];
> struct hrtimer timer;
> };
>
> #else
>
> struct ha_monitor { };
>
> #endif /* CONFIG_RV_HA_MONITOR */
That's why I wanted also LTL to be optionally empty, technically we
could do the same for DA but since it's the smallest it's rather
pointless.
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
2025-08-21 12:38 ` Nam Cao
@ 2025-08-25 9:55 ` Nam Cao
2025-08-25 14:24 ` Gabriele Monaco
2025-08-25 10:06 ` Nam Cao
2 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-25 9:55 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> for i in event.split("\\n"):
> + if ";" in i:
> + # if the event contains a constraint (hybrid automata),
> + # it will be separated by a ";":
> + # "sched_switch;x<1000;reset(x)"
> + line = i.split(";")
> + i = line.pop(0)
> + if len(line) > 2:
> + raise ValueError("Only 1 constraint and 1 reset are supported")
> + envs += self.__extract_env_var(line)
> events.append(i)
How about we get rid of the (if ";"), and just split it:
for i in event.split("\\n"):
# if the event contains a constraint (hybrid automata),
# it will be separated by a ";":
# "sched_switch;x<1000;reset(x)"
line = i.split(";")
events.append(line.pop(0))
if len(line) > 2:
raise ValueError("Only 1 constraint and 1 reset are supported")
envs += self.__extract_env_var(line)
> + 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]
I know I complained about regex last week, but for this case I think regex
is more suitable:
state = re.findall(r'".*?" \[label = "([^"]*)"\]', self.__dot_lines[cursor])[0]
> + if "\\n" in state:
> + line = state.split("\\n")
> + line.pop(0)
> + if len(line) > 1:
> + raise ValueError("Only 1 constraint is supported in the state")
> + envs += self.__extract_env_var([line[0].replace(" ", "")])
Same as above, I think we can just split without the if check.
> cursor += 1
>
> - return sorted(set(events))
> -
> - def __create_matrix(self):
> + 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)
If && and || are the only things you intend to support, then this is
probably okay. But if the syntax will ever be extended (e.g. brackets),
this becomes unreadable really fast.
Perhaps a "real" parser which converts the input string into abstract
syntax tree is something worth considering.
> + def is_event_constraint(self, key: tuple[int, int] | int) -> 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, tuple)
I don't love this. A few years from now, someone could change state
constraint to be a tuple, or change event contraint to not be tuple, and
things break in confusing ways.
Perhaps an explicit variable to store contraint type information instead?
> - 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:
> @@ -36,7 +37,7 @@ class Dot2c(Automata):
>
> return buff
>
> - def format_states_enum(self):
> + def format_states_enum(self) -> list[str]:
> buff = []
> buff.append("enum %s {" % self.enum_states_def)
> buff += self.__get_enum_states_content()
> @@ -58,7 +59,7 @@ class Dot2c(Automata):
>
> return buff
>
> - def format_events_enum(self):
> + def format_events_enum(self) -> list[str]:
These changes should be in your type annotation patch?
> buff = []
> buff.append("enum %s {" % self.enum_events_def)
> buff += self.__get_enum_events_content()
> @@ -66,7 +67,43 @@ class Dot2c(Automata):
>
> return buff
>
> - def get_minimun_type(self):
> + 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 = []
> + first = True
> + # 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:
> + if first:
> + buff.append("\t%s%s = 0," % (env, self.enum_suffix))
> + first = False
> + else:
> + buff.append("\t%s%s," % (env, self.enum_suffix))
The "= 0" assignment for the first enum is not required right? Perhaps you
can get rid of the 'first" thingy, and just do
for env in list(self.env_stored) + unstored:
buff.append("\t%s%s," % (env, self.enum_suffix))
> + match unit:
> + case "us":
> + value *= 1000
> + case "ms":
> + value *= 1000000
> + case "s":
> + value *= 1000000000
Since when did Python have this? Nice!
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
2025-08-21 12:38 ` Nam Cao
2025-08-25 9:55 ` Nam Cao
@ 2025-08-25 10:06 ` Nam Cao
2025-08-25 14:02 ` Gabriele Monaco
2 siblings, 1 reply; 57+ messages in thread
From: Nam Cao @ 2025-08-25 10:06 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> + def fill_constr_func(self) -> list[str]:
> + buff = []
> + if self.constraints:
> + buff.append(
> +"""/*
> + * This function is used to validate state transitions.
> + *
> + * It is generated by parsing the model, there is usually no need to change it,
> + * unless conditions were incorrectly specified
If the conditions were incorrectly specified, then they should be fixed in
the automaton, not fixed in this generated C code.
> or too complex for the parser.
Do you have examples of these "too complex" cases? Is there a plan to
handle them?
> + * If the monitor requires a timer, this function is responsible to arm it when
> + * the next state has a constraint and cancel it in any other case. Transitions
> + * to the same state never affect timers.
> + */
> +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)
> +{
> +\tbool res = true;
> +""" % (self.enum_states_def, self.enum_events_def, self.enum_states_def))
> + buff += self.__get_constr_condition()
> + buff += self.__get_state_constr_condition()
> + buff.append("""\treturn res;
> +}\n""")
> + return buff
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-25 10:06 ` Nam Cao
@ 2025-08-25 14:02 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-25 14:02 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Mon, 2025-08-25 at 12:06 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> > + def fill_constr_func(self) -> list[str]:
> > + buff = []
> > + if self.constraints:
> > + buff.append(
> > +"""/*
> > + * This function is used to validate state transitions.
> > + *
> > + * It is generated by parsing the model, there is usually no need
> > to change it,
> > + * unless conditions were incorrectly specified
>
> If the conditions were incorrectly specified, then they should be
> fixed in the automaton, not fixed in this generated C code.
You're right, I should reword or remove that.
> > or too complex for the parser.
>
> Do you have examples of these "too complex" cases? Is there a plan to
> handle them?
I wrote this before having the function/macro types, now everything
coming to my mind is covered. Anyway the parser is trying to be smart
but may not catch absolutely everything the user wants to specify.
Now the best thing to do would probably be to strictly define a grammar
and not support what isn't included in there, rather than telling the
user they can kind of tweak the function..
Good points, thanks!
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata
2025-08-25 9:55 ` Nam Cao
@ 2025-08-25 14:24 ` Gabriele Monaco
0 siblings, 0 replies; 57+ messages in thread
From: Gabriele Monaco @ 2025-08-25 14:24 UTC (permalink / raw)
To: Nam Cao
Cc: linux-kernel, Steven Rostedt, linux-trace-kernel, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On Mon, 2025-08-25 at 11:55 +0200, Nam Cao wrote:
> On Thu, Aug 14, 2025 at 05:08:02PM +0200, Gabriele Monaco wrote:
> > for i in event.split("\\n"):
> > + if ";" in i:
> > + # if the event contains a constraint
> > (hybrid automata),
> > + # it will be separated by a ";":
> > + # "sched_switch;x<1000;reset(x)"
> > + line = i.split(";")
> > + i = line.pop(0)
> > + if len(line) > 2:
> > + raise ValueError("Only 1 constraint
> > and 1 reset are supported")
> > + envs += self.__extract_env_var(line)
> > events.append(i)
>
> How about we get rid of the (if ";"), and just split it:
>
> for i in event.split("\\n"):
> # if the event contains a constraint (hybrid automata),
> # it will be separated by a ";":
> # "sched_switch;x<1000;reset(x)"
> line = i.split(";")
> events.append(line.pop(0))
> if len(line) > 2:
> raise ValueError("Only 1 constraint and 1 reset are
> supported")
> envs += self.__extract_env_var(line)
>
Right, that's neater, thanks.
> > + 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]
>
> I know I complained about regex last week, but for this case I think
> regex is more suitable:
>
> state = re.findall(r'".*?" \[label = "([^"]*)"\]',
> self.__dot_lines[cursor])[0]
>
Yeah I guess I opened the pandora box already..
Also thinking about the ply parser, it'd probably end up relying on
regex too.
I may just set up the things in this patch (use regex where too complex
without) and re-evaluate the whole things with ply later on.
> > + if "\\n" in state:
> > + line = state.split("\\n")
> > + line.pop(0)
> > + if len(line) > 1:
> > + raise ValueError("Only 1 constraint is
> > supported in the state")
> > + envs +=
> > self.__extract_env_var([line[0].replace(" ", "")])
>
> Same as above, I think we can just split without the if check.
>
> > cursor += 1
> >
> > - return sorted(set(events))
> > -
> > - def __create_matrix(self):
> > + 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)
>
> If && and || are the only things you intend to support, then this is
> probably okay. But if the syntax will ever be extended (e.g.
> brackets),
> this becomes unreadable really fast.
>
> Perhaps a "real" parser which converts the input string into abstract
> syntax tree is something worth considering.
Yeah totally, I'm going to stick to the "simple" syntax for now and
then rewrite the whole thing with a proper parser.
>
> > + def is_event_constraint(self, key: tuple[int, int] | int) ->
> > 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, tuple)
>
> I don't love this. A few years from now, someone could change state
> constraint to be a tuple, or change event contraint to not be tuple,
> and things break in confusing ways.
>
> Perhaps an explicit variable to store contraint type information
> instead?
Mmh good point, I'll look into that.
>
> > - 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:
> > @@ -36,7 +37,7 @@ class Dot2c(Automata):
> >
> > return buff
> >
> > - def format_states_enum(self):
> > + def format_states_enum(self) -> list[str]:
> > buff = []
> > buff.append("enum %s {" % self.enum_states_def)
> > buff += self.__get_enum_states_content()
> > @@ -58,7 +59,7 @@ class Dot2c(Automata):
> >
> > return buff
> >
> > - def format_events_enum(self):
> > + def format_events_enum(self) -> list[str]:
>
> These changes should be in your type annotation patch?
Right, probably coming from yet another rebase, having a look.
>
> > buff = []
> > buff.append("enum %s {" % self.enum_events_def)
> > buff += self.__get_enum_events_content()
> > @@ -66,7 +67,43 @@ class Dot2c(Automata):
> >
> > return buff
> >
> > - def get_minimun_type(self):
> > + 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 = []
> > + first = True
> > + # 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:
> > + if first:
> > + buff.append("\t%s%s = 0," % (env,
> > self.enum_suffix))
> > + first = False
> > + else:
> > + buff.append("\t%s%s," % (env, self.enum_suffix))
>
> The "= 0" assignment for the first enum is not required right?
> Perhaps you can get rid of the 'first" thingy, and just do
>
> for env in list(self.env_stored) + unstored:
> buff.append("\t%s%s," % (env, self.enum_suffix))
>
Right, that's covered by the standard, we could just remove it.
> > + match unit:
> > + case "us":
> > + value *= 1000
> > + case "ms":
> > + value *= 1000000
> > + case "s":
> > + value *= 1000000000
>
> Since when did Python have this? Nice!
I think it was 3.10 . Honestly, it hasn't had it for way too long!
Thanks,
Gabriele
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton
2025-08-14 15:08 ` [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
@ 2025-09-02 9:28 ` Nam Cao
0 siblings, 0 replies; 57+ messages in thread
From: Nam Cao @ 2025-09-02 9:28 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, Steven Rostedt, Jonathan Corbet, Masami Hiramatsu,
linux-trace-kernel, linux-doc, Tomas Glozar, Juri Lelli,
Clark Williams, John Kacur
On Thu, Aug 14, 2025 at 05:08:05PM +0200, Gabriele Monaco wrote:
> +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 <-----------------------------------------------+
> + #=========#
Yeah, I like this much better. The previous monitor looks like it is easily
broken by any kernel refactoring.
Nam
^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [RFC PATCH 14/17] sched: Add deadline tracepoints
2025-08-19 14:02 ` Juri Lelli
2025-08-19 14:21 ` Gabriele Monaco
2025-08-19 14:38 ` Phil Auld
@ 2025-09-02 14:55 ` Juri Lelli
2 siblings, 0 replies; 57+ messages in thread
From: Juri Lelli @ 2025-09-02 14:55 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Peter Zijlstra, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Ingo Molnar, linux-trace-kernel, Nam Cao, Tomas Glozar,
Juri Lelli, Clark Williams, John Kacur
On 19/08/25 16:02, Juri Lelli wrote:
> On 19/08/25 12:34, Gabriele Monaco wrote:
...
> > > As per the whole _tp() thing, you can attach to the actual
> > > sched_switch tracepoint with a module and read whatever you want.
> >
> > Yeah I believe Juri referred to model constraints on the already
> > existing events rather than new tracepoints here.
>
> Separately from this series, maybe we should put such a module/bpf thing
> somewhere shared, so it's easier to use it when needed.
Maybe we could
---
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 383cfc684e8e..994b6973d77d 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -117,6 +117,10 @@ 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_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);
---
so that the new tps can be used from modules like sched_tp Phil
mentioned?
Thanks,
Juri
^ permalink raw reply related [flat|nested] 57+ messages in thread
end of thread, other threads:[~2025-09-02 14:55 UTC | newest]
Thread overview: 57+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-14 15:07 [RFC PATCH 00/17] rv: Add Hybrid Automata monitor type, per-object and deadline monitors Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 01/17] rv: Refactor da_monitor to minimise macros Gabriele Monaco
2025-08-21 8:14 ` Nam Cao
2025-08-21 8:49 ` Gabriele Monaco
2025-08-25 8:02 ` Gabriele Monaco
2025-08-25 8:03 ` Nam Cao
2025-08-14 15:07 ` [RFC PATCH 02/17] rv: Cleanup da_monitor after refactor Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 03/17] Documentation/rv: Adapt documentation after da_monitor refactoring Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 04/17] verification/rvgen: Adapt dot2k and templates after refactoring da_monitor.h Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 05/17] verification/rvgen: Annotate DA functions with types Gabriele Monaco
2025-08-21 8:29 ` Nam Cao
2025-08-21 8:51 ` Gabriele Monaco
2025-08-14 15:07 ` [RFC PATCH 06/17] verification/dot2c: Remove __buff_to_string() and cleanup Gabriele Monaco
2025-08-21 8:32 ` Nam Cao
2025-08-14 15:07 ` [RFC PATCH 07/17] verification/rvgen: Remove unused variable declaration from containers Gabriele Monaco
2025-08-21 8:33 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 08/17] rv: Add Hybrid Automata monitor type Gabriele Monaco
2025-08-19 9:18 ` Juri Lelli
2025-08-19 9:48 ` Gabriele Monaco
2025-08-19 10:08 ` Juri Lelli
2025-08-19 10:53 ` Gabriele Monaco
2025-08-21 12:18 ` Nam Cao
2025-08-25 7:48 ` Gabriele Monaco
2025-08-25 8:13 ` Nam Cao
2025-08-25 8:32 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 09/17] verification/rvgen: Allow spaces in and events strings Gabriele Monaco
2025-08-21 12:22 ` Nam Cao
2025-08-21 13:22 ` Gabriele Monaco
2025-08-21 15:15 ` Nam Cao
2025-08-21 15:58 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 10/17] verification/rvgen: Add support for Hybrid Automata Gabriele Monaco
2025-08-21 12:38 ` Nam Cao
2025-08-21 13:15 ` Gabriele Monaco
2025-08-25 9:55 ` Nam Cao
2025-08-25 14:24 ` Gabriele Monaco
2025-08-25 10:06 ` Nam Cao
2025-08-25 14:02 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 11/17] Documentation/rv: Add documentation about hybrid automata Gabriele Monaco
2025-08-19 8:53 ` Juri Lelli
2025-08-19 9:14 ` Juri Lelli
2025-08-19 10:46 ` Gabriele Monaco
2025-08-19 10:40 ` Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 12/17] rv: Add sample hybrid monitors stall Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 13/17] rv: Convert the opid monitor to a hybrid automaton Gabriele Monaco
2025-09-02 9:28 ` Nam Cao
2025-08-14 15:08 ` [RFC PATCH 14/17] sched: Add deadline tracepoints Gabriele Monaco
2025-08-19 9:56 ` Juri Lelli
2025-08-19 10:12 ` Peter Zijlstra
2025-08-19 10:34 ` Gabriele Monaco
2025-08-19 14:02 ` Juri Lelli
2025-08-19 14:21 ` Gabriele Monaco
2025-08-19 14:38 ` Phil Auld
2025-08-20 5:20 ` Juri Lelli
2025-09-02 14:55 ` Juri Lelli
2025-08-14 15:08 ` [RFC PATCH 15/17] rv: Add support for per-object monitors in DA/HA Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 16/17] verification/rvgen: Add support for per-obj monitors Gabriele Monaco
2025-08-14 15:08 ` [RFC PATCH 17/17] rv: Add deadline monitors 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).