* Re: [PATCH v4 2/2] tracing: Drain deferred trigger frees if kthread creation fails
From: Steven Rostedt @ 2026-03-28 2:30 UTC (permalink / raw)
To: Wesley Atwell
Cc: linux-trace-kernel, linux-kernel, mhiramat, mark.rutland,
mathieu.desnoyers, tom.zanussi
In-Reply-To: <CAN=sVvzFMC1m2aGT23aRpPpoddBVP59mQBmEsQyEPKBYm3J_Vw@mail.gmail.com>
On Fri, 27 Mar 2026 16:41:52 -0600
Wesley Atwell <atwellwea@gmail.com> wrote:
> Yes,
>
> This kernel command line reliably reaches trigger_data_free() during boot:
>
> trace_event=sched:sched_switch
> trace_trigger=sched_switch.traceon,sched_switch.traceon
>
> On an unpatched tree, that crashes during early boot before userspace.
> The call trace goes through:
>
> trigger_data_free()
> __kthread_create_on_node()
> try_to_wake_up()
>
> The stack also shows the boot-time trigger registration path:
>
> event_trigger_parse()
> trigger_process_regex()
> __trace_early_add_events()
Thanks for this. I can reproduce the crash. I'm also going to add this
to the change log as it is useful (I'll even add it to one of my
regression tests). I'll take this patch separately (this didn't need to
be a patch series, as the two patches do not depend on each other).
-- Steve
^ permalink raw reply
* Re: [PATCH v4 2/2] tracing: Drain deferred trigger frees if kthread creation fails
From: Wesley Atwell @ 2026-03-28 4:56 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-trace-kernel, linux-kernel, mhiramat, mark.rutland,
mathieu.desnoyers, tom.zanussi
In-Reply-To: <20260327223022.167defcc@robin>
Hi Steve,
I'm glad the test case was helpful. I'll include similar testing
details in future commit messages and avoid grouping unrelated
patches.
Thanks,
Wesley Atwell
^ permalink raw reply
* [PATCH] tracing: Remove tracing_alloc_snapshot() when snapshot isn't defined
From: Steven Rostedt @ 2026-03-28 14:19 UTC (permalink / raw)
To: LKML, Linux trace kernel; +Cc: Masami Hiramatsu, Mathieu Desnoyers, Mark Brown
From: Steven Rostedt <rostedt@goodmis.org>
The function tracing_alloc_snapshot() is only used between trace.c and
trace_snapshot.c. When snapshot isn't configured, it's not used at all.
The stub function was defined as a global with no users and no prototype
causing build issues.
Remove the function when snapshot isn't configured as nothing is calling
it.
Also remove the EXPORT_SYMBOL_GPL() that was associated with it as it's
not used outside of the tracing subsystem which also includes any modules.
Reported-by: Mark Brown <broonie@kernel.org>
Closes: https://lore.kernel.org/all/acb-IuZ4vDkwwQLW@sirena.co.uk/
Fixes: bade44fe546212 (tracing: Move snapshot code out of trace.c and into trace_snapshot.c)
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
kernel/trace/trace.c | 7 -------
1 file changed, 7 deletions(-)
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index ec2b926436a7..7f2fbf9c36df 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -810,19 +810,12 @@ int tracing_alloc_snapshot(void)
return ret;
}
-EXPORT_SYMBOL_GPL(tracing_alloc_snapshot);
#else
void tracing_snapshot(void)
{
WARN_ONCE(1, "Snapshot feature not enabled, but internal snapshot used");
}
EXPORT_SYMBOL_GPL(tracing_snapshot);
-int tracing_alloc_snapshot(void)
-{
- WARN_ONCE(1, "Snapshot feature not enabled, but snapshot allocation used");
- return -ENODEV;
-}
-EXPORT_SYMBOL_GPL(tracing_alloc_snapshot);
void tracing_snapshot_alloc(void)
{
/* Give warning */
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v4 1/2] tracing: Preserve repeated boot-time tracing parameters
From: Steven Rostedt @ 2026-03-28 17:53 UTC (permalink / raw)
To: Wesley Atwell
Cc: linux-trace-kernel, linux-kernel, mhiramat, mark.rutland,
mathieu.desnoyers, tom.zanussi
In-Reply-To: <20260324221326.1395799-2-atwellwea@gmail.com>
On Tue, 24 Mar 2026 16:13:25 -0600
Wesley Atwell <atwellwea@gmail.com> wrote:
> +++ b/kernel/trace/trace.c
> @@ -228,6 +228,34 @@ static int boot_instance_index;
> static char boot_snapshot_info[COMMAND_LINE_SIZE] __initdata;
> static int boot_snapshot_index;
>
> +/*
> + * Repeated boot parameters, including Bootconfig array expansions, need
> + * to stay in the delimiter form that the existing parser consumes.
> + */
> +void __init trace_append_boot_param(char *buf, const char *str, char sep,
> + size_t size)
> +{
> + size_t len, str_len;
Why use the "size_t" type? Just use int. Then you don't need to play games
about unsigned types in the if statements below. The boot cmdline will
never come close to being 2GB in size.
> +
> + if (!buf[0]) {
Should we check for size here? Perhaps just remove this part (see below)
> + strscpy(buf, str, size);
> + return;
> + }
> +
> + str_len = strlen(str);
> + if (!str_len)
> + return;
> +
> + len = strlen(buf);
> + if (len >= size - 1)
> + return;
> + if (str_len >= size - len - 1)
> + return;
Instead of the above, have:
/* Plus 2 for ",\0" */
if (str_len + len + 2 > size)
return;
> +
> + buf[len] = sep;
If we remove the first check, here we can have:
if (len)
buf[len++] = sep;
By adding one to length, it makes the strscpy() a bit more readable.
strscpy(buf + len, str, size - len);
> + strscpy(buf + len + 1, str, size - len - 1);
> +}
> +
-- Steve
^ permalink raw reply
* [PATCH v5] tracing: Preserve repeated boot-time tracing parameters
From: Wesley Atwell @ 2026-03-28 20:18 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mark Rutland, Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
Wesley Atwell
In-Reply-To: <20260324221326.1395799-2-atwellwea@gmail.com>
Some tracing boot parameters already accept delimited value lists, but
their __setup() handlers keep only the last instance seen at boot.
Make repeated instances append to the same boot-time buffer in the
format each parser already consumes.
Use a shared trace_append_boot_param() helper for the ftrace filters,
trace_options, and kprobe_event boot parameters. trace_trigger=
tokenizes its backing storage in place, so keep a running offset and
only parse the newly appended chunk into bootup_triggers[].
This also lets Bootconfig array values work naturally when they expand
to repeated param=value entries.
Validated by booting with repeated ftrace_filter=, ftrace_notrace=,
ftrace_graph_filter=, ftrace_graph_notrace=, trace_options=,
kprobe_event=, and trace_trigger= parameters and confirming that the
resulting tracefs state preserved every requested entry. Before this
change, only the last instance from each repeated parameter survived
boot.
Signed-off-by: Wesley Atwell <atwellwea@gmail.com>
---
v5:
- use int sizes in the shared append helper and trace_trigger bookkeeping
- keep a single bounded append path that only inserts the separator after
the first entry
- only advance the trace_trigger buffer offset after a successful append
kernel/trace/ftrace.c | 12 ++++++++----
kernel/trace/trace.c | 29 ++++++++++++++++++++++++++++-
kernel/trace/trace.h | 2 ++
kernel/trace/trace_events.c | 23 ++++++++++++++++++++---
kernel/trace/trace_kprobe.c | 3 ++-
5 files changed, 60 insertions(+), 9 deletions(-)
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 413310912609..8bd3dd1d549c 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6841,7 +6841,8 @@ bool ftrace_filter_param __initdata;
static int __init set_ftrace_notrace(char *str)
{
ftrace_filter_param = true;
- strscpy(ftrace_notrace_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_notrace_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_notrace=", set_ftrace_notrace);
@@ -6849,7 +6850,8 @@ __setup("ftrace_notrace=", set_ftrace_notrace);
static int __init set_ftrace_filter(char *str)
{
ftrace_filter_param = true;
- strscpy(ftrace_filter_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_filter_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_filter=", set_ftrace_filter);
@@ -6861,14 +6863,16 @@ static int ftrace_graph_set_hash(struct ftrace_hash *hash, char *buffer);
static int __init set_graph_function(char *str)
{
- strscpy(ftrace_graph_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_graph_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_graph_filter=", set_graph_function);
static int __init set_graph_notrace_function(char *str)
{
- strscpy(ftrace_graph_notrace_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_graph_notrace_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_graph_notrace=", set_graph_notrace_function);
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index a626211ceb9a..c8cf45dc4152 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -228,6 +228,32 @@ static int boot_instance_index;
static char boot_snapshot_info[COMMAND_LINE_SIZE] __initdata;
static int boot_snapshot_index;
+/*
+ * Repeated boot parameters, including Bootconfig array expansions, need
+ * to stay in the delimiter form that the existing parser consumes.
+ */
+void __init trace_append_boot_param(char *buf, const char *str, char sep,
+ int size)
+{
+ int len, needed, str_len;
+
+ if (!*str)
+ return;
+
+ len = strlen(buf);
+ str_len = strlen(str);
+ needed = len + str_len + 1;
+ if (len)
+ needed++;
+ if (needed > size)
+ return;
+
+ if (len)
+ buf[len++] = sep;
+
+ strscpy(buf + len, str, size - len);
+}
+
static int __init set_cmdline_ftrace(char *str)
{
strscpy(bootup_tracer_buf, str, MAX_TRACER_SIZE);
@@ -329,7 +355,8 @@ static char trace_boot_options_buf[MAX_TRACER_SIZE] __initdata;
static int __init set_trace_boot_options(char *str)
{
- strscpy(trace_boot_options_buf, str, MAX_TRACER_SIZE);
+ trace_append_boot_param(trace_boot_options_buf, str, ',',
+ MAX_TRACER_SIZE);
return 1;
}
__setup("trace_options=", set_trace_boot_options);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index b8f3804586a0..237a0417de1c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -863,6 +863,8 @@ extern int DYN_FTRACE_TEST_NAME(void);
extern int DYN_FTRACE_TEST_NAME2(void);
extern void trace_set_ring_buffer_expanded(struct trace_array *tr);
+void __init trace_append_boot_param(char *buf, const char *str,
+ char sep, int size);
extern bool tracing_selftest_disabled;
#ifdef CONFIG_FTRACE_STARTUP_TEST
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 249d1cba72c0..7f0bec4622f6 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -3679,20 +3679,37 @@ static struct boot_triggers {
} bootup_triggers[MAX_BOOT_TRIGGERS];
static char bootup_trigger_buf[COMMAND_LINE_SIZE];
+static int bootup_trigger_buf_len;
static int nr_boot_triggers;
static __init int setup_trace_triggers(char *str)
{
+ char *slot;
char *trigger;
char *buf;
int i;
- strscpy(bootup_trigger_buf, str, COMMAND_LINE_SIZE);
+ if (bootup_trigger_buf_len >= COMMAND_LINE_SIZE)
+ return 1;
+
+ slot = bootup_trigger_buf + bootup_trigger_buf_len;
+
+ /*
+ * trace_trigger= parsing tokenizes the backing storage in place.
+ * Copy each repeated parameter into fresh space and only parse that
+ * newly copied chunk here.
+ */
+ trace_append_boot_param(slot, str, '\0',
+ COMMAND_LINE_SIZE - bootup_trigger_buf_len);
+ if (!*slot)
+ return 1;
+
+ bootup_trigger_buf_len += strlen(slot) + 1;
trace_set_ring_buffer_expanded(NULL);
disable_tracing_selftest("running event triggers");
- buf = bootup_trigger_buf;
- for (i = 0; i < MAX_BOOT_TRIGGERS; i++) {
+ buf = slot;
+ for (i = nr_boot_triggers; i < MAX_BOOT_TRIGGERS; i++) {
trigger = strsep(&buf, ",");
if (!trigger)
break;
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index a5dbb72528e0..e9f1c55aea64 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -31,7 +31,8 @@ static char kprobe_boot_events_buf[COMMAND_LINE_SIZE] __initdata;
static int __init set_kprobe_boot_events(char *str)
{
- strscpy(kprobe_boot_events_buf, str, COMMAND_LINE_SIZE);
+ trace_append_boot_param(kprobe_boot_events_buf, str, ';',
+ COMMAND_LINE_SIZE);
disable_tracing_selftest("running kprobe events");
return 1;
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] tracing/hist: bound full field-name construction
From: Pengpeng Hou @ 2026-03-29 3:09 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, tom.zanussi
Cc: linux-kernel, linux-trace-kernel, pengpeng
hist_field_name() builds a fully qualified synthetic field name in a
fixed MAX_FILTER_STR_VAL buffer using repeated strcat() calls. Long
system, event, and field names can therefore overflow the static staging
buffer.
Build the qualified name with snprintf() and fall back to the plain
field name if it does not fit.
Fixes: 067fe038e70f ("tracing: Add variable reference handling to hist triggers")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
kernel/trace/trace_events_hist.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 73ea180cad55..4a27da628a71 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -1362,12 +1362,12 @@ static const char *hist_field_name(struct hist_field *field,
if (field->system) {
static char full_name[MAX_FILTER_STR_VAL];
- strcat(full_name, field->system);
- strcat(full_name, ".");
- strcat(full_name, field->event_name);
- strcat(full_name, ".");
- strcat(full_name, field->name);
- field_name = full_name;
+ if (snprintf(full_name, sizeof(full_name), "%s.%s.%s",
+ field->system, field->event_name,
+ field->name) < sizeof(full_name))
+ field_name = full_name;
+ else
+ field_name = field->name;
} else
field_name = field->name;
} else if (field->flags & HIST_FIELD_FL_TIMESTAMP)
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH 2/2] tracing/hist: allocate synthetic-field command buffers to fit
From: Pengpeng Hou @ 2026-03-29 3:09 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, tom.zanussi
Cc: linux-kernel, linux-trace-kernel, pengpeng
The synthetic field helpers currently build temporary names and trigger
commands in fixed MAX_FILTER_STR_VAL buffers with strcpy() and strcat().
Long field names, key lists, or saved filters can therefore overrun
those staging buffers while constructing the synthetic histogram
command.
Allocate the synthetic name and command buffers to the exact size
required by the current histogram instead of relying on fixed-size
scratch storage.
Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
kernel/trace/trace_events_hist.c | 46 +++++++++++++++++++-------------
1 file changed, 28 insertions(+), 18 deletions(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 4a27da628a71..1883bd6d9b95 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -2964,13 +2964,10 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data,
struct hist_field *event_var;
char *synthetic_name;
- synthetic_name = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
+ synthetic_name = kasprintf(GFP_KERNEL, "synthetic_%s", field_name);
if (!synthetic_name)
return ERR_PTR(-ENOMEM);
- strcpy(synthetic_name, "synthetic_");
- strcat(synthetic_name, field_name);
-
event_var = find_event_var(target_hist_data, system, event_name, synthetic_name);
kfree(synthetic_name);
@@ -3016,6 +3013,8 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
struct hist_field *event_var;
char *saved_filter;
char *cmd;
+ size_t cmdlen;
+ size_t off;
int ret;
if (target_hist_data->n_field_var_hists >= SYNTH_FIELDS_MAX) {
@@ -3053,35 +3052,46 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
if (!var_hist)
return ERR_PTR(-ENOMEM);
- cmd = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
+ saved_filter = find_trigger_filter(hist_data, file);
+
+ cmdlen = strlen("keys=") + strlen(":synthetic_") +
+ strlen(field_name) + strlen("=") + strlen(field_name) + 1;
+ first = true;
+ for_each_hist_key_field(i, hist_data) {
+ key_field = hist_data->fields[i];
+ if (!first)
+ cmdlen++;
+ cmdlen += strlen(key_field->field->name);
+ first = false;
+ }
+
+ if (saved_filter)
+ cmdlen += strlen(" if ") + strlen(saved_filter);
+
+ cmd = kzalloc(cmdlen, GFP_KERNEL);
if (!cmd) {
kfree(var_hist);
return ERR_PTR(-ENOMEM);
}
/* Use the same keys as the compatible histogram */
- strcat(cmd, "keys=");
+ off = scnprintf(cmd, cmdlen, "keys=");
+ first = true;
for_each_hist_key_field(i, hist_data) {
key_field = hist_data->fields[i];
- if (!first)
- strcat(cmd, ",");
- strcat(cmd, key_field->field->name);
+ off += scnprintf(cmd + off, cmdlen - off, "%s%s",
+ first ? "" : ",", key_field->field->name);
first = false;
}
/* Create the synthetic field variable specification */
- strcat(cmd, ":synthetic_");
- strcat(cmd, field_name);
- strcat(cmd, "=");
- strcat(cmd, field_name);
+ off += scnprintf(cmd + off, cmdlen - off, ":synthetic_%s=%s",
+ field_name, field_name);
/* Use the same filter as the compatible histogram */
- saved_filter = find_trigger_filter(hist_data, file);
- if (saved_filter) {
- strcat(cmd, " if ");
- strcat(cmd, saved_filter);
- }
+ if (saved_filter)
+ scnprintf(cmd + off, cmdlen - off, " if %s", saved_filter);
var_hist->cmd = kstrdup(cmd, GFP_KERNEL);
if (!var_hist->cmd) {
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* Re: [PATCH v5] tracing: Preserve repeated boot-time tracing parameters
From: Steven Rostedt @ 2026-03-29 15:49 UTC (permalink / raw)
To: Wesley Atwell
Cc: Masami Hiramatsu, Mark Rutland, Mathieu Desnoyers, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260328201842.1782806-1-atwellwea@gmail.com>
On Sat, 28 Mar 2026 14:18:42 -0600
Wesley Atwell <atwellwea@gmail.com> wrote:
> Some tracing boot parameters already accept delimited value lists, but
> their __setup() handlers keep only the last instance seen at boot.
> Make repeated instances append to the same boot-time buffer in the
> format each parser already consumes.
>
> Use a shared trace_append_boot_param() helper for the ftrace filters,
> trace_options, and kprobe_event boot parameters. trace_trigger=
> tokenizes its backing storage in place, so keep a running offset and
> only parse the newly appended chunk into bootup_triggers[].
>
> This also lets Bootconfig array values work naturally when they expand
> to repeated param=value entries.
>
> Validated by booting with repeated ftrace_filter=, ftrace_notrace=,
> ftrace_graph_filter=, ftrace_graph_notrace=, trace_options=,
> kprobe_event=, and trace_trigger= parameters and confirming that the
> resulting tracefs state preserved every requested entry. Before this
> change, only the last instance from each repeated parameter survived
> boot.
>
> Signed-off-by: Wesley Atwell <atwellwea@gmail.com>
> ---
> v5:
FYI, it's nice to have a daisy chain connection of previous versions. I
suggest instead of just saying "v5:" use:
Changes since v4: https://lore.kernel.org/all/20260324221326.1395799-2-atwellwea@gmail.com/
> - use int sizes in the shared append helper and trace_trigger bookkeeping
> - keep a single bounded append path that only inserts the separator after
> the first entry
> - only advance the trace_trigger buffer offset after a successful append
>
> kernel/trace/ftrace.c | 12 ++++++++----
> kernel/trace/trace.c | 29 ++++++++++++++++++++++++++++-
> kernel/trace/trace.h | 2 ++
> kernel/trace/trace_events.c | 23 ++++++++++++++++++++---
> kernel/trace/trace_kprobe.c | 3 ++-
> 5 files changed, 60 insertions(+), 9 deletions(-)
>
> +/*
> + * Repeated boot parameters, including Bootconfig array expansions, need
> + * to stay in the delimiter form that the existing parser consumes.
> + */
> +void __init trace_append_boot_param(char *buf, const char *str, char sep,
> + int size)
> +{
This is much better.
> + int len, needed, str_len;
> +
> + if (!*str)
> + return;
> +
> + len = strlen(buf);
> + str_len = strlen(str);
> + needed = len + str_len + 1;
Perhaps add a comment:
/* For continuation, account for separator */
> + if (len)
> + needed++;
> + if (needed > size)
> + return;
> +
> + if (len)
> + buf[len++] = sep;
> +
> + strscpy(buf + len, str, size - len);
> +}
> +
> static int __init set_cmdline_ftrace(char *str)
> {
> strscpy(bootup_tracer_buf, str, MAX_TRACER_SIZE);
> --- a/kernel/trace/trace_events.c
> +++ b/kernel/trace/trace_events.c
> @@ -3679,20 +3679,37 @@ static struct boot_triggers {
> } bootup_triggers[MAX_BOOT_TRIGGERS];
>
> static char bootup_trigger_buf[COMMAND_LINE_SIZE];
> +static int bootup_trigger_buf_len;
> static int nr_boot_triggers;
>
> static __init int setup_trace_triggers(char *str)
> {
> + char *slot;
> char *trigger;
> char *buf;
> int i;
>
> - strscpy(bootup_trigger_buf, str, COMMAND_LINE_SIZE);
> + if (bootup_trigger_buf_len >= COMMAND_LINE_SIZE)
> + return 1;
> +
> + slot = bootup_trigger_buf + bootup_trigger_buf_len;
The bootup_trigger_buf is a temporary buffer for this function only. It
works fine as is. There's no reason to modify this function.
-- Steve
> +
> + /*
> + * trace_trigger= parsing tokenizes the backing storage in place.
> + * Copy each repeated parameter into fresh space and only parse that
> + * newly copied chunk here.
> + */
> + trace_append_boot_param(slot, str, '\0',
> + COMMAND_LINE_SIZE - bootup_trigger_buf_len);
> + if (!*slot)
> + return 1;
> +
> + bootup_trigger_buf_len += strlen(slot) + 1;
> trace_set_ring_buffer_expanded(NULL);
> disable_tracing_selftest("running event triggers");
>
> - buf = bootup_trigger_buf;
> - for (i = 0; i < MAX_BOOT_TRIGGERS; i++) {
> + buf = slot;
> + for (i = nr_boot_triggers; i < MAX_BOOT_TRIGGERS; i++) {
> trigger = strsep(&buf, ",");
> if (!trigger)
> break;
^ permalink raw reply
* Re: [GIT PULL] RTLA changes for v7.1
From: Steven Rostedt @ 2026-03-29 16:22 UTC (permalink / raw)
To: Tomas Glozar
Cc: Costa Shulyupin, Wander Lairson Costa, LKML, linux-trace-kernel
In-Reply-To: <20260327150237.405973-1-tglozar@redhat.com>
On Fri, 27 Mar 2026 16:02:37 +0100
Tomas Glozar <tglozar@redhat.com> wrote:
> Steven,
>
> please pull the following changes for RTLA (more info in tag description).
I can pull this but I just noticed that starting with 7.0-rc1, it fails
to build without libbpf:
Auto-detecting system features:
... libtraceevent: [ on ]
... libtracefs: [ on ]
... libcpupower: [ OFF ]
... libbpf: [ OFF ]
... clang-bpf-co-re: [ on ]
... bpftool-skeletons: [ OFF ]
libcpupower is missing, building without --deepest-idle-state support.
Please install libcpupower-dev/kernel-tools-libs-devel
libbpf is missing, building without BPF skeleton support.
Please install libbpf-dev/libbpf-devel
bpftool is missing or not supporting skeletons, building without BPF skeleton support.
Please install bpftool
make -f /work/git/linux-trace.git/tools/build/Makefile.build dir=. obj=rtla
make[1]: Entering directory '/work/git/linux-trace.git/tools/tracing/rtla'
make[2]: Entering directory '/work/git/linux-trace.git/tools/tracing/rtla'
CC /work/git/linux-trace.git/tools/tracing/rtla/src/trace.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/utils.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/actions.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/common.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/osnoise.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/osnoise_top.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/osnoise_hist.o
CC /work/git/linux-trace.git/tools/tracing/rtla/src/timerlat.o
In file included from src/timerlat.c:18:
src/timerlat_bpf.h:15:10: fatal error: bpf/libbpf.h: No such file or directory
15 | #include <bpf/libbpf.h>
| ^~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [/work/git/linux-trace.git/tools/build/Makefile.build:94: /work/git/linux-trace.git/tools/tracing/rtla/src/timerlat.o] Error 1
make[2]: Leaving directory '/work/git/linux-trace.git/tools/tracing/rtla'
make[1]: *** [/work/git/linux-trace.git/tools/build/Makefile.build:156: src] Error 2
make[1]: Leaving directory '/work/git/linux-trace.git/tools/tracing/rtla'
make: *** [Makefile:104: /work/git/linux-trace.git/tools/tracing/rtla/rtla-in.o] Error 2
That should probably be fixed on top of v7.0-rcX so that it is not
broken in 7.0.
-- Steve
>
> Thanks,
> Tomas
>
> The following changes since commit 11439c4635edd669ae435eec308f4ab8a0804808:
>
> Linux 7.0-rc2 (2026-03-01 15:39:31 -0800)
>
> are available in the Git repository at:
>
> git://git.kernel.org/pub/scm/linux/kernel/git/tglozar/linux.git tags/rtla-v7.1
>
> for you to fetch changes up to 82374995b63d2de21414163828a32d52610dcaf2:
>
> Documentation/rtla: Document SIGINT behavior (2026-03-27 10:58:30 +0100)
^ permalink raw reply
* Re: [PATCH 1/2] tracing/hist: bound full field-name construction
From: Steven Rostedt @ 2026-03-29 18:39 UTC (permalink / raw)
To: Pengpeng Hou
Cc: mhiramat, mathieu.desnoyers, tom.zanussi, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260329030950.32503-1-pengpeng@iscas.ac.cn>
On Sun, 29 Mar 2026 11:09:49 +0800
Pengpeng Hou <pengpeng@iscas.ac.cn> wrote:
> hist_field_name() builds a fully qualified synthetic field name in a
> fixed MAX_FILTER_STR_VAL buffer using repeated strcat() calls. Long
> system, event, and field names can therefore overflow the static staging
> buffer.
>
> Build the qualified name with snprintf() and fall back to the plain
> field name if it does not fit.
The fallback breaks
>
> Fixes: 067fe038e70f ("tracing: Add variable reference handling to hist triggers")
Do you have any examples where it actually does overflow or is this just theoretical?
If it's just theoretical, it does not get a "Fixes" tag.
Hmm, but actually I don't see it resetting full_name to a '\0' so I can see
this concatenating on top of a previous value. THAT would need fixing and
require a fixes tag. An example of triggering the overflow would also be
required.
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> kernel/trace/trace_events_hist.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
> index 73ea180cad55..4a27da628a71 100644
> --- a/kernel/trace/trace_events_hist.c
> +++ b/kernel/trace/trace_events_hist.c
> @@ -1362,12 +1362,12 @@ static const char *hist_field_name(struct hist_field *field,
> if (field->system) {
> static char full_name[MAX_FILTER_STR_VAL];
>
> - strcat(full_name, field->system);
> - strcat(full_name, ".");
> - strcat(full_name, field->event_name);
> - strcat(full_name, ".");
> - strcat(full_name, field->name);
> - field_name = full_name;
> + if (snprintf(full_name, sizeof(full_name), "%s.%s.%s",
> + field->system, field->event_name,
> + field->name) < sizeof(full_name))
Ug, please do not use a horribly looking if conditional. And it should most
definitely error on overflow: Break it up:
if (field->system) {
static char full_name[MAX_FILTER_STR_VAL];
int len;
len = snprintf(full_name, sizeof(full_name), "%s.%s.%s",
field->system, field->event_name,
field->name);
if (len >= size(full_name))
return NULL;
field_name = full_name;
> + field_name = full_name;
> + else
> + field_name = field->name;
> } else
> field_name = field->name;
> } else if (field->flags & HIST_FIELD_FL_TIMESTAMP)
-- Steve
^ permalink raw reply
* [PATCH v6] tracing: Preserve repeated boot-time tracing parameters
From: Wesley Atwell @ 2026-03-29 18:42 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mark Rutland, Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
Wesley Atwell
In-Reply-To: <20260328201842.1782806-1-atwellwea@gmail.com>
Some tracing boot parameters already accept delimited value lists, but
their __setup() handlers keep only the last instance seen at boot.
Make repeated instances append to the same boot-time buffer in the
format each parser already consumes.
Use a shared trace_append_boot_param() helper for the ftrace filters,
trace_options, and kprobe_event boot parameters. trace_trigger=
still tokenizes a temporary parse buffer in place, but now copies each
parsed event/trigger pair into boot-time storage so repeated instances
do not overwrite earlier ones.
This also lets Bootconfig array values work naturally when they expand
to repeated param=value entries.
Before this change, only the last instance from each repeated
parameter survived boot.
Signed-off-by: Wesley Atwell <atwellwea@gmail.com>
---
Changes since v5: https://lore.kernel.org/all/20260328201842.1782806-1-atwellwea@gmail.com/
- add the separator accounting comment in trace_append_boot_param()
- keep the existing trace_trigger= temporary buffer and copy each
parsed event/trigger pair into boot-time storage instead of tracking
a running offset inside that buffer
kernel/trace/ftrace.c | 12 ++++++++----
kernel/trace/trace.c | 30 +++++++++++++++++++++++++++++-
kernel/trace/trace.h | 2 ++
kernel/trace/trace_events.c | 24 +++++++++++++++++++++---
kernel/trace/trace_kprobe.c | 3 ++-
5 files changed, 62 insertions(+), 9 deletions(-)
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 413310912609..8bd3dd1d549c 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6841,7 +6841,8 @@ bool ftrace_filter_param __initdata;
static int __init set_ftrace_notrace(char *str)
{
ftrace_filter_param = true;
- strscpy(ftrace_notrace_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_notrace_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_notrace=", set_ftrace_notrace);
@@ -6849,7 +6850,8 @@ __setup("ftrace_notrace=", set_ftrace_notrace);
static int __init set_ftrace_filter(char *str)
{
ftrace_filter_param = true;
- strscpy(ftrace_filter_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_filter_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_filter=", set_ftrace_filter);
@@ -6861,14 +6863,16 @@ static int ftrace_graph_set_hash(struct ftrace_hash *hash, char *buffer);
static int __init set_graph_function(char *str)
{
- strscpy(ftrace_graph_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_graph_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_graph_filter=", set_graph_function);
static int __init set_graph_notrace_function(char *str)
{
- strscpy(ftrace_graph_notrace_buf, str, FTRACE_FILTER_SIZE);
+ trace_append_boot_param(ftrace_graph_notrace_buf, str, ',',
+ FTRACE_FILTER_SIZE);
return 1;
}
__setup("ftrace_graph_notrace=", set_graph_notrace_function);
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index a626211ceb9a..2fbfcb506b24 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -228,6 +228,33 @@ static int boot_instance_index;
static char boot_snapshot_info[COMMAND_LINE_SIZE] __initdata;
static int boot_snapshot_index;
+/*
+ * Repeated boot parameters, including Bootconfig array expansions, need
+ * to stay in the delimiter form that the existing parser consumes.
+ */
+void __init trace_append_boot_param(char *buf, const char *str, char sep,
+ int size)
+{
+ int len, needed, str_len;
+
+ if (!*str)
+ return;
+
+ len = strlen(buf);
+ str_len = strlen(str);
+ needed = len + str_len + 1;
+ /* For continuation, account for the separator. */
+ if (len)
+ needed++;
+ if (needed > size)
+ return;
+
+ if (len)
+ buf[len++] = sep;
+
+ strscpy(buf + len, str, size - len);
+}
+
static int __init set_cmdline_ftrace(char *str)
{
strscpy(bootup_tracer_buf, str, MAX_TRACER_SIZE);
@@ -329,7 +356,8 @@ static char trace_boot_options_buf[MAX_TRACER_SIZE] __initdata;
static int __init set_trace_boot_options(char *str)
{
- strscpy(trace_boot_options_buf, str, MAX_TRACER_SIZE);
+ trace_append_boot_param(trace_boot_options_buf, str, ',',
+ MAX_TRACER_SIZE);
return 1;
}
__setup("trace_options=", set_trace_boot_options);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index b8f3804586a0..237a0417de1c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -863,6 +863,8 @@ extern int DYN_FTRACE_TEST_NAME(void);
extern int DYN_FTRACE_TEST_NAME2(void);
extern void trace_set_ring_buffer_expanded(struct trace_array *tr);
+void __init trace_append_boot_param(char *buf, const char *str,
+ char sep, int size);
extern bool tracing_selftest_disabled;
#ifdef CONFIG_FTRACE_STARTUP_TEST
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 249d1cba72c0..1c4a4a46169e 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -17,6 +17,7 @@
#include <linux/kthread.h>
#include <linux/tracefs.h>
#include <linux/uaccess.h>
+#include <linux/memblock.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/sort.h>
@@ -3674,7 +3675,7 @@ trace_create_new_event(struct trace_event_call *call,
#define MAX_BOOT_TRIGGERS 32
static struct boot_triggers {
- const char *event;
+ char *event;
char *trigger;
} bootup_triggers[MAX_BOOT_TRIGGERS];
@@ -3683,6 +3684,7 @@ static int nr_boot_triggers;
static __init int setup_trace_triggers(char *str)
{
+ char *event;
char *trigger;
char *buf;
int i;
@@ -3692,14 +3694,30 @@ static __init int setup_trace_triggers(char *str)
disable_tracing_selftest("running event triggers");
buf = bootup_trigger_buf;
- for (i = 0; i < MAX_BOOT_TRIGGERS; i++) {
+ for (i = nr_boot_triggers; i < MAX_BOOT_TRIGGERS; i++) {
trigger = strsep(&buf, ",");
if (!trigger)
break;
- bootup_triggers[i].event = strsep(&trigger, ".");
+ event = strsep(&trigger, ".");
bootup_triggers[i].trigger = trigger;
if (!bootup_triggers[i].trigger)
break;
+
+ /*
+ * Keep each parsed trigger outside the temporary setup
+ * buffer so repeated trace_trigger= entries do not
+ * overwrite earlier ones.
+ */
+ bootup_triggers[i].event =
+ memblock_alloc_or_panic(strlen(event) + 1,
+ SMP_CACHE_BYTES);
+ strscpy(bootup_triggers[i].event, event,
+ strlen(event) + 1);
+ bootup_triggers[i].trigger =
+ memblock_alloc_or_panic(strlen(trigger) + 1,
+ SMP_CACHE_BYTES);
+ strscpy(bootup_triggers[i].trigger, trigger,
+ strlen(trigger) + 1);
}
nr_boot_triggers = i;
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index a5dbb72528e0..e9f1c55aea64 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -31,7 +31,8 @@ static char kprobe_boot_events_buf[COMMAND_LINE_SIZE] __initdata;
static int __init set_kprobe_boot_events(char *str)
{
- strscpy(kprobe_boot_events_buf, str, COMMAND_LINE_SIZE);
+ trace_append_boot_param(kprobe_boot_events_buf, str, ';',
+ COMMAND_LINE_SIZE);
disable_tracing_selftest("running kprobe events");
return 1;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 2/2] tracing/hist: allocate synthetic-field command buffers to fit
From: Steven Rostedt @ 2026-03-29 18:49 UTC (permalink / raw)
To: Pengpeng Hou
Cc: mhiramat, mathieu.desnoyers, tom.zanussi, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260329030950.32503-2-pengpeng@iscas.ac.cn>
On Sun, 29 Mar 2026 11:09:50 +0800
Pengpeng Hou <pengpeng@iscas.ac.cn> wrote:
> The synthetic field helpers currently build temporary names and trigger
> commands in fixed MAX_FILTER_STR_VAL buffers with strcpy() and strcat().
> Long field names, key lists, or saved filters can therefore overrun
> those staging buffers while constructing the synthetic histogram
> command.
>
> Allocate the synthetic name and command buffers to the exact size
> required by the current histogram instead of relying on fixed-size
> scratch storage.
No, the names should never be greater than the max value defined. If they
are, then it should error out.
>
> Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'")
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> kernel/trace/trace_events_hist.c | 46 +++++++++++++++++++-------------
> 1 file changed, 28 insertions(+), 18 deletions(-)
>
> diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
> index 4a27da628a71..1883bd6d9b95 100644
> --- a/kernel/trace/trace_events_hist.c
> +++ b/kernel/trace/trace_events_hist.c
> @@ -2964,13 +2964,10 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data,
> struct hist_field *event_var;
> char *synthetic_name;
>
Should be preceded with:
if (strlen("synthetic_") + strlen(field_name) >= MAX_FILTER_STR_VAL)
return ERR_PTR(-E2BIG);
> - synthetic_name = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
> + synthetic_name = kasprintf(GFP_KERNEL, "synthetic_%s", field_name);
> if (!synthetic_name)
> return ERR_PTR(-ENOMEM);
>
> - strcpy(synthetic_name, "synthetic_");
> - strcat(synthetic_name, field_name);
> -
> event_var = find_event_var(target_hist_data, system, event_name, synthetic_name);
>
> kfree(synthetic_name);
> @@ -3016,6 +3013,8 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
> struct hist_field *event_var;
> char *saved_filter;
> char *cmd;
> + size_t cmdlen;
> + size_t off;
> int ret;
>
> if (target_hist_data->n_field_var_hists >= SYNTH_FIELDS_MAX) {
> @@ -3053,35 +3052,46 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
> if (!var_hist)
> return ERR_PTR(-ENOMEM);
>
> - cmd = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
> + saved_filter = find_trigger_filter(hist_data, file);
> +
> + cmdlen = strlen("keys=") + strlen(":synthetic_") +
> + strlen(field_name) + strlen("=") + strlen(field_name) + 1;
> + first = true;
> + for_each_hist_key_field(i, hist_data) {
> + key_field = hist_data->fields[i];
> + if (!first)
> + cmdlen++;
> + cmdlen += strlen(key_field->field->name);
> + first = false;
> + }
> +
> + if (saved_filter)
> + cmdlen += strlen(" if ") + strlen(saved_filter);
> +
Length should be checked. There shouldn't be huge strings for filters.
Perhaps in the future we may make them bigger but for now, 256 bytes should
be the limit.
-- Steve
> + cmd = kzalloc(cmdlen, GFP_KERNEL);
> if (!cmd) {
> kfree(var_hist);
> return ERR_PTR(-ENOMEM);
> }
>
> /* Use the same keys as the compatible histogram */
> - strcat(cmd, "keys=");
> + off = scnprintf(cmd, cmdlen, "keys=");
> + first = true;
>
> for_each_hist_key_field(i, hist_data) {
> key_field = hist_data->fields[i];
> - if (!first)
> - strcat(cmd, ",");
> - strcat(cmd, key_field->field->name);
> + off += scnprintf(cmd + off, cmdlen - off, "%s%s",
> + first ? "" : ",",
> key_field->field->name); first = false;
> }
>
> /* Create the synthetic field variable specification */
> - strcat(cmd, ":synthetic_");
> - strcat(cmd, field_name);
> - strcat(cmd, "=");
> - strcat(cmd, field_name);
> + off += scnprintf(cmd + off, cmdlen - off, ":synthetic_%s=%s",
> + field_name, field_name);
>
> /* Use the same filter as the compatible histogram */
> - saved_filter = find_trigger_filter(hist_data, file);
> - if (saved_filter) {
> - strcat(cmd, " if ");
> - strcat(cmd, saved_filter);
> - }
> + if (saved_filter)
> + scnprintf(cmd + off, cmdlen - off, " if %s",
> saved_filter);
> var_hist->cmd = kstrdup(cmd, GFP_KERNEL);
> if (!var_hist->cmd) {
^ permalink raw reply
* Re: [PATCH v13 4/4] ring-buffer: Add persistent ring buffer selftest
From: Masami Hiramatsu @ 2026-03-30 1:42 UTC (permalink / raw)
To: Steven Rostedt
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, Ian Rogers
In-Reply-To: <20260327162508.6cac690c@gandalf.local.home>
On Fri, 27 Mar 2026 16:25:08 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> On Wed, 25 Mar 2026 11:25:25 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> >
> > Add a self-destractive test for the persistent ring buffer. This
> > will invalidate some sub-buffer pages in the persistent ring buffer
> > when kernel gets panic, and check whether the number of detected
> > invalid pages and the total entry_bytes are the same as record
> > after reboot.
> >
> > This can ensure the kernel correctly recover partially corrupted
> > persistent ring buffer when boot.
> >
> > The test only runs on the persistent ring buffer whose name is
> > "ptracingtest". And user has to fill it up with events before
> > kernel panics.
> >
> > To run the test, enable CONFIG_RING_BUFFER_PERSISTENT_SELFTEST
>
> I think a more appropriate config name would be:
>
> CONFIG_PERSISTENT_RING_BUFFER_ERROR_INJECT
>
> as that's what it is doing as it is only testing error injection and not
> the persistent ring buffer.
OK, selftest will be another implementation.
(preparing buffer with test data and check recovery process?)
>
> > and you have to setup the kernel cmdline;
> >
> > reserve_mem=20M:2M:trace trace_instance=ptracingtest^traceoff@trace
> > panic=1
> >
> > And run following commands after the 1st boot;
> >
> > cd /sys/kernel/tracing/instances/ptracingtest
> > echo 1 > tracing_on
> > echo 1 > events/enable
> > sleep 3
> > echo c > /proc/sysrq-trigger
>
> These instructions should probably be in the CONFIG help message.
OK. I'll add it.
>
> >
> > After panic message, the kernel will reboot and run the verification
> > on the persistent ring buffer, e.g.
> >
> > Ring buffer meta [2] invalid buffer page detected
> > Ring buffer meta [2] is from previous boot! (318 pages discarded)
> > Ring buffer testing [2] invalid pages: PASSED (318/318)
> > Ring buffer testing [2] entry_bytes: PASSED (1300476/1300476)
>
> BTW, when I tested this, I got the above on the first boot, but if I
> rebooted normally without re-enabling the persistent ring buffer, I would
> get on the next boot:
Hmm, since it is already recovered (rewound) the 2nd rewound process
may not work correctly. Let me fix it.
>
>
> [ 0.966510] Ring buffer meta [2] is from previous boot! (0 pages discarded)
> [ 0.971338] #2
> [ 1.003431] Ring buffer meta [3] is from previous boot! (0 pages discarded)
> [ 1.007737] #3
> [ 1.039091] Ring buffer meta [4] is from previous boot! (0 pages discarded)
> [ 1.043181] Ring buffer testing [4] invalid pages: FAILED (0/1597)
> [ 1.044660] Ring buffer testing [4] entry_bytes: PASSED (6512464/6512464)
> [ 1.047829] #4
> [ 1.079811] Ring buffer meta [5] is from previous boot! (0 pages discarded)
> [ 1.083728] #5
> [ 1.116764] Ring buffer meta [6] is from previous boot! (0 pages discarded)
> [ 1.120846] #6
> [ 1.156502] Ring buffer meta [7] is from previous boot! (0 pages discarded)
> [ 1.160857] #7
>
> I'll start testing the previous 3 patches and may add them to next.
Thanks,
>
> Also, I noticed that there's nothing that reads the RB_MISSING as I thought
> it might. I'll have to look into how to pass that info to the trace output.
>
> -- Steve
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH v2 2/2] tracing/hist: reject synthetic-field strings that exceed MAX_FILTER_STR_VAL
From: Pengpeng Hou @ 2026-03-30 2:46 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, tom.zanussi
Cc: linux-kernel, linux-trace-kernel, pengpeng
In-Reply-To: <20260329030950.32503-2-pengpeng@iscas.ac.cn>
The synthetic field helpers build a prefixed synthetic field name and a
hist trigger command in fixed MAX_FILTER_STR_VAL staging buffers. Even
when each individual field or filter string stays within that limit, the
combined "keys=...:synthetic_...=... if ..." command can exceed 256
bytes and overrun the scratch buffer.
Keep MAX_FILTER_STR_VAL as the tracing-side limit and reject synthetic
field names or generated commands that do not fit in that bound before
formatting them into the fixed buffers.
Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
v2:
- keep MAX_FILTER_STR_VAL as the fixed tracing-side limit
- reject overlong synthetic names and generated commands with -E2BIG
- drop the previous dynamic-allocation approach
kernel/trace/trace_events_hist.c | 57 +++++++++++++++++++++++---------
1 file changed, 41 insertions(+), 16 deletions(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index f9c8a4f078ea..4172c91605af 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -2966,12 +2966,16 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data,
struct hist_field *event_var;
char *synthetic_name;
+ if ((sizeof("synthetic_") - 1) + strlen(field_name) >=
+ MAX_FILTER_STR_VAL)
+ return ERR_PTR(-E2BIG);
+
synthetic_name = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
if (!synthetic_name)
return ERR_PTR(-ENOMEM);
- strcpy(synthetic_name, "synthetic_");
- strcat(synthetic_name, field_name);
+ scnprintf(synthetic_name, MAX_FILTER_STR_VAL, "synthetic_%s",
+ field_name);
event_var = find_event_var(target_hist_data, system, event_name, synthetic_name);
@@ -3018,6 +3022,8 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
struct hist_field *event_var;
char *saved_filter;
char *cmd;
+ size_t cmdlen;
+ size_t off;
int ret;
if (target_hist_data->n_field_var_hists >= SYNTH_FIELDS_MAX) {
@@ -3048,13 +3054,36 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
/* See if a synthetic field variable has already been created */
event_var = find_synthetic_field_var(target_hist_data, subsys_name,
event_name, field_name);
- if (!IS_ERR_OR_NULL(event_var))
+ if (IS_ERR(event_var))
+ return event_var;
+ if (event_var)
return event_var;
var_hist = kzalloc_obj(*var_hist);
if (!var_hist)
return ERR_PTR(-ENOMEM);
+ saved_filter = find_trigger_filter(hist_data, file);
+
+ cmdlen = strlen("keys=") + strlen(":synthetic_") +
+ strlen(field_name) + strlen("=") + strlen(field_name);
+ first = true;
+ for_each_hist_key_field(i, hist_data) {
+ key_field = hist_data->fields[i];
+ if (!first)
+ cmdlen++;
+ cmdlen += strlen(key_field->field->name);
+ first = false;
+ }
+
+ if (saved_filter)
+ cmdlen += strlen(" if ") + strlen(saved_filter);
+
+ if (cmdlen >= MAX_FILTER_STR_VAL) {
+ kfree(var_hist);
+ return ERR_PTR(-E2BIG);
+ }
+
cmd = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
if (!cmd) {
kfree(var_hist);
@@ -3062,28 +3091,24 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
}
/* Use the same keys as the compatible histogram */
- strcat(cmd, "keys=");
+ off = scnprintf(cmd, MAX_FILTER_STR_VAL, "keys=");
+ first = true;
for_each_hist_key_field(i, hist_data) {
key_field = hist_data->fields[i];
- if (!first)
- strcat(cmd, ",");
- strcat(cmd, key_field->field->name);
+ off += scnprintf(cmd + off, MAX_FILTER_STR_VAL - off, "%s%s",
+ first ? "" : ",", key_field->field->name);
first = false;
}
/* Create the synthetic field variable specification */
- strcat(cmd, ":synthetic_");
- strcat(cmd, field_name);
- strcat(cmd, "=");
- strcat(cmd, field_name);
+ off += scnprintf(cmd + off, MAX_FILTER_STR_VAL - off,
+ ":synthetic_%s=%s", field_name, field_name);
/* Use the same filter as the compatible histogram */
- saved_filter = find_trigger_filter(hist_data, file);
- if (saved_filter) {
- strcat(cmd, " if ");
- strcat(cmd, saved_filter);
- }
+ if (saved_filter)
+ scnprintf(cmd + off, MAX_FILTER_STR_VAL - off, " if %s",
+ saved_filter);
var_hist->cmd = kstrdup(cmd, GFP_KERNEL);
if (!var_hist->cmd) {
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v2 1/2] tracing/hist: rebuild full_name on each hist_field_name() call
From: Pengpeng Hou @ 2026-03-30 2:46 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, tom.zanussi
Cc: linux-kernel, linux-trace-kernel, pengpeng
In-Reply-To: <20260329030950.32503-1-pengpeng@iscas.ac.cn>
hist_field_name() uses a static MAX_FILTER_STR_VAL buffer for fully
qualified variable-reference names, but it currently appends into that
buffer with strcat() without rebuilding it first. As a result, repeated
calls append a new "system.event.field" name onto the previous one,
which can eventually run past the end of full_name.
Build the name with snprintf() on each call and return NULL if the fully
qualified name does not fit in MAX_FILTER_STR_VAL.
Fixes: 067fe038e70f ("tracing: Add variable reference handling to hist triggers")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
v2:
- rebuild full_name on each call instead of falling back to field->name
- return NULL on overflow as suggested
- split out the snprintf() length check instead of using an inline if
kernel/trace/trace_events_hist.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 73ea180cad55..f9c8a4f078ea 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -1361,12 +1361,14 @@ static const char *hist_field_name(struct hist_field *field,
field->flags & HIST_FIELD_FL_VAR_REF) {
if (field->system) {
static char full_name[MAX_FILTER_STR_VAL];
+ int len;
+
+ len = snprintf(full_name, sizeof(full_name), "%s.%s.%s",
+ field->system, field->event_name,
+ field->name);
+ if (len >= sizeof(full_name))
+ return NULL;
- strcat(full_name, field->system);
- strcat(full_name, ".");
- strcat(full_name, field->event_name);
- strcat(full_name, ".");
- strcat(full_name, field->name);
field_name = full_name;
} else
field_name = field->name;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH 1/4] tracing/probe: reject empty immediate strings
From: Pengpeng Hou @ 2026-03-30 6:29 UTC (permalink / raw)
To: rostedt
Cc: mhiramat, mathieu.desnoyers, linux-trace-kernel, linux-kernel,
pengpeng
parse_probe_arg() treats an argument starting with \\" as an
immediate string and passes arg + 2 to __parse_imm_string(). If the
argument contains only the opener, __parse_imm_string() computes
strlen(str) as 0 and then dereferences str[len - 1], reading one byte
before the string.
Reject empty immediate-string bodies before checking the closing quote.
Fixes: a42e3c4de964 ("tracing/probe: Add immediate string parameter support")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
kernel/trace/trace_probe.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index e0a5dc86c07e..e1c73065dae5 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -1068,7 +1068,7 @@ static int __parse_imm_string(char *str, char **pbuf, int offs)
{
size_t len = strlen(str);
- if (str[len - 1] != '"') {
+ if (!len || str[len - 1] != '"') {
trace_probe_log_err(offs + len, IMMSTR_NO_CLOSE);
return -EINVAL;
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* Re: [PATCH v13 4/4] ring-buffer: Add persistent ring buffer selftest
From: Masami Hiramatsu @ 2026-03-30 8:47 UTC (permalink / raw)
To: Steven Rostedt
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, Ian Rogers
In-Reply-To: <20260327164748.67b6453d@gandalf.local.home>
On Fri, 27 Mar 2026 16:47:48 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> On Fri, 27 Mar 2026 16:25:08 -0400
> Steven Rostedt <rostedt@goodmis.org> wrote:
>
> > Also, I noticed that there's nothing that reads the RB_MISSING as I thought
> > it might. I'll have to look into how to pass that info to the trace output.
>
> And when I cat /sys/kernel/tracing/instances/ptracingtest/per_cpu/cpuX/trace_pipe
I tried this but it works
~ # cat /sys/kernel/tracing/instances/ptracingtest/per_cpu/cpu5/stats
entries: 36198
overrun: 0
commit overrun: 0
bytes: 1301360
oldest event ts: 24.796202
now ts: 48.613676
dropped events: 0
read events: 0
~ # cat /sys/kernel/tracing/instances/ptracingtest/per_cpu/cpu5/trace_pipe >> /dev/null
~ # cat /sys/kernel/tracing/instances/ptracingtest/per_cpu/cpu5/stats
entries: 0
overrun: 0
commit overrun: 0
bytes: 52
oldest event ts: 27.931273
now ts: 71.443017
dropped events: 0
read events: 36198
>
> (where X is the failed buffer)
>
> It triggered an infinite loop of:
>
> [ 206.549217] ------------[ cut here ]------------
> [ 206.550907] WARNING: kernel/trace/ring_buffer.c:5751 at __rb_get_reader_page+0xa6b/0x1040, CPU#2: cat/1197
> [ 206.554111] Modules linked in:
> [ 206.555331] CPU: 2 UID: 0 PID: 1197 Comm: cat Tainted: G W 7.0.0-rc4-test-00028-g7b37f48b2c57-dirty #276 PREEMPT(full)
> [ 206.559048] Tainted: [W]=WARN
> [ 206.560244] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> [ 206.563212] RIP: 0010:__rb_get_reader_page+0xa6b/0x1040
> [ 206.564964] Code: ff df 48 c1 ea 03 80 3c 02 00 0f 85 4a 05 00 00 48 8b 43 10 be 04 00 00 00 4c 8d 60 08 4c 89 e7 e8 9a 2d 63 00 f0 41 ff 04 24 <0f> 0b e9 36 fb ff ff e8 29 39 05 00 fb 0f 1f 44 00 00 4d 85 f6 0f
> [ 206.572295] RSP: 0018:ffff888112a77938 EFLAGS: 00010006
> [ 206.574095] RAX: 0000000000000001 RBX: ffff888100d6e000 RCX: 0000000000000001
> [ 206.576458] RDX: 0000000000000001 RSI: 0000000000000004 RDI: ffff88810027b808
> [ 206.578749] RBP: 1ffff1102254ef34 R08: ffffffff909a1556 R09: ffffed102004f701
> [ 206.581020] R10: ffffed102004f702 R11: ffff88823443a000 R12: ffff88810027b808
> [ 206.583312] R13: ffff888100f65f00 R14: ffff888100f65f00 R15: dffffc0000000000
> [ 206.585647] FS: 00007f98e4d80780(0000) GS:ffff88829e3c2000(0000) knlGS:0000000000000000
> [ 206.588246] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [ 206.590179] CR2: 00007f98e4d3e000 CR3: 000000012272e006 CR4: 0000000000172ef0
> [ 206.592444] Call Trace:
> [ 206.593518] <TASK>
> [ 206.594436] ? __pfx___rb_get_reader_page+0x10/0x10
> [ 206.596148] ? lock_acquire+0x1b2/0x340
> [ 206.597599] rb_buffer_peek+0x37e/0x520
> [ 206.598954] ring_buffer_peek+0xe9/0x310
> [ 206.601956] peek_next_entry+0x15a/0x280
> [ 206.603420] __find_next_entry+0x39f/0x530
> [ 206.604918] ? __pfx___mutex_lock+0x10/0x10
> [ 206.606474] ? rcu_is_watching+0x15/0xb0
> [ 206.616049] ? __pfx___find_next_entry+0x10/0x10
> [ 206.617741] ? preempt_count_sub+0x10c/0x1c0
> [ 206.619242] ? __pfx_down_read+0x10/0x10
> [ 206.620687] trace_find_next_entry_inc+0x2f/0x240
> [ 206.622351] tracing_read_pipe+0x4e7/0xc60
> [ 206.623852] ? rw_verify_area+0x353/0x5f0
> [ 206.625325] vfs_read+0x171/0xb20
> [ 206.626592] ? __lock_acquire+0x487/0x2220
> [ 206.628135] ? __pfx___handle_mm_fault+0x10/0x10
> [ 206.629784] ? __pfx_vfs_read+0x10/0x10
> [ 206.632696] ? __pfx_css_rstat_updated+0x10/0x10
> [ 206.634351] ? rcu_is_watching+0x15/0xb0
> [ 206.635835] ? trace_preempt_on+0x126/0x160
> [ 206.637362] ? preempt_count_sub+0x10c/0x1c0
> [ 206.638880] ? count_memcg_events+0x10a/0x4b0
> [ 206.640455] ? find_held_lock+0x2b/0x80
> [ 206.641908] ? rcu_read_unlock+0x17/0x60
> [ 206.643340] ? lock_release+0x1ab/0x320
> [ 206.644812] ksys_read+0xff/0x200
> [ 206.646127] ? __pfx_ksys_read+0x10/0x10
> [ 206.647651] do_syscall_64+0x117/0x16c0
> [ 206.649035] ? irqentry_exit+0xd9/0x690
> [ 206.650548] entry_SYSCALL_64_after_hwframe+0x76/0x7e
> [ 206.652331] RIP: 0033:0x7f98e4e14eb2
> [ 206.653743] Code: 18 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 75 1a 83 e2 39 83 fa 08 75 12 e8 2b ff ff ff 0f 1f 00 49 89 ca 48 8b 44 24 20 0f 05 <48> 83 c4 18 c3 66 0f 1f 84 00 00 00 00 00 48 83 ec 10 ff 74 24 18
> [ 206.659364] RSP: 002b:00007ffdc0a8d930 EFLAGS: 00000202 ORIG_RAX: 0000000000000000
> [ 206.663251] RAX: ffffffffffffffda RBX: 0000000000040000 RCX: 00007f98e4e14eb2
> [ 206.665614] RDX: 0000000000040000 RSI: 00007f98e4d3f000 RDI: 0000000000000003
> [ 206.668022] RBP: 0000000000040000 R08: 0000000000000000 R09: 0000000000000000
> [ 206.670306] R10: 0000000000000000 R11: 0000000000000202 R12: 00007f98e4d3f000
> [ 206.672624] R13: 0000000000000003 R14: 0000000000000000 R15: 0000000000040000
> [ 206.674941] </TASK>
> [ 206.675927] irq event stamp: 7898
> [ 206.677154] hardirqs last enabled at (7897): [<ffffffff90991f6f>] ring_buffer_empty_cpu+0x19f/0x2f0
> [ 206.680088] hardirqs last disabled at (7898): [<ffffffff909a277d>] ring_buffer_peek+0x17d/0x310
> [ 206.682881] softirqs last enabled at (7888): [<ffffffff9056cffc>] handle_softirqs+0x5bc/0x7c0
> [ 206.685710] softirqs last disabled at (7879): [<ffffffff9056d322>] __irq_exit_rcu+0x112/0x230
> [ 206.688483] ---[ end trace 0000000000000000 ]---
>
> OK, that RB_MISSED_EVENTS is causing an issue. Something else we need to
> look into. The warning is that __rb_get_reader_page() is trying more than 3
> times. Thus I think it's constantly swapping the head page and the reader
> page. Something to investigate.
In this version, it does not set RB_MISSED_EVENTS on invalid pages.
However, it ignores that bit when validating it.
static int rb_validate_buffer(struct buffer_page *bpage, int cpu,
struct ring_buffer_cpu_meta *meta)
{
[...]
/*
* When a sub-buffer is recovered from a read, the commit value may
* have RB_MISSED_* bits set, as these bits are reset on reuse.
* Even after clearing these bits, a commit value greater than the
* subbuf_size is considered invalid.
*/
tail = local_read(&dpage->commit) & ~RB_MISSED_MASK;
if (tail <= meta->subbuf_size)
ret = rb_read_data_buffer(dpage, tail, cpu, &ts, &delta);
But it does not remove the RB_MISSED_EVENTS flag from commit if
the page is *VALID*. (it is cleared only if the page is invalid)
Thus, if the page originally has the RB_MISSED_EVENTS, the recovery
process does not remove it, and reader may cause infinite loop.
I think in any case, these flags should be removed when it is valided.
Thank you,
>
> So, I'm holding off pulling in these patches. I may take the first one
> though.
>
> -- Steve
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* Re: [PATCH v2 4/9] mm: move free_reserved_area() to mm/memblock.c
From: Vlastimil Babka (SUSE) @ 2026-03-30 9:00 UTC (permalink / raw)
To: Mike Rapoport, Andrew Morton
Cc: Alexander Potapenko, Alexander Viro, Andreas Larsson,
Ard Biesheuvel, Borislav Petkov, Brendan Jackman,
Christophe Leroy (CS GROUP), Catalin Marinas, Christian Brauner,
David S. Miller, Dave Hansen, David Hildenbrand, Dmitry Vyukov,
Ilias Apalodimas, Ingo Molnar, Jan Kara, Johannes Weiner,
Liam R. Howlett, Lorenzo Stoakes, Madhavan Srinivasan,
Marco Elver, Marek Szyprowski, Masami Hiramatsu, Michael Ellerman,
Michal Hocko, Nicholas Piggin, H. Peter Anvin, Rob Herring,
Robin Murphy, Saravana Kannan, Suren Baghdasaryan,
Thomas Gleixner, Will Deacon, Zi Yan, devicetree, iommu,
kasan-dev, linux-arm-kernel, linux-efi, linux-fsdevel,
linux-kernel, linux-mm, linux-trace-kernel, linuxppc-dev,
sparclinux, x86
In-Reply-To: <20260323074836.3653702-5-rppt@kernel.org>
On 3/23/26 08:48, Mike Rapoport wrote:
> From: "Mike Rapoport (Microsoft)" <rppt@kernel.org>
>
> free_reserved_area() is related to memblock as it frees reserved memory
> back to the buddy allocator, similar to what memblock_free_late() does.
>
> Move free_reserved_area() to mm/memblock.c to prepare for further
> consolidation of the functions that free reserved memory.
>
> No functional changes.
>
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
^ permalink raw reply
* [PATCH] rtla: Fix build without libbpf header
From: Tomas Glozar @ 2026-03-30 9:12 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
rtla supports building without libbpf. However, BPF actions
patchset [1] adds an include of bpf/libbpf.h into timerlat_bpf.h,
which breaks build on systems that don't have libbpf headers
installed.
This is a leftover from a draft version of the patchset where
timerlat_bpf_set_action() (which takes a struct bpf_program * argument)
was defined in the header. timerlat_bpf.c already includes bpf/libbpf.h
via timerlat.skel.h when libbpf is present.
Remove the redundant include to fix build on systems without libbpf
headers.
[1] https://lore.kernel.org/linux-trace-kernel/20251126144205.331954-1-tglozar@redhat.com/T/
Reported-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Closes: https://lore.kernel.org/linux-trace-kernel/20260329122202.65a8b575@robin/
Fixes: 8cd0f08ac72e ("rtla/timerlat: Support tail call from BPF program")
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/timerlat_bpf.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/tools/tracing/rtla/src/timerlat_bpf.h b/tools/tracing/rtla/src/timerlat_bpf.h
index 169abeaf4363..f7c5675737fe 100644
--- a/tools/tracing/rtla/src/timerlat_bpf.h
+++ b/tools/tracing/rtla/src/timerlat_bpf.h
@@ -12,7 +12,6 @@ enum summary_field {
};
#ifndef __bpf__
-#include <bpf/libbpf.h>
#ifdef HAVE_BPF_SKEL
int timerlat_bpf_init(struct timerlat_params *params);
int timerlat_bpf_attach(void);
--
2.53.0
^ permalink raw reply related
* [PATCH v8 00/12] rv: Add Hybrid Automata monitor type, per-object and deadline monitors
From: Gabriele Monaco @ 2026-03-30 11:09 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli
Cc: Gabriele Monaco, Tomas Glozar, Clark Williams, John Kacur,
linux-trace-kernel
This series contains several related changes, the main areas are:
* 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 nomiss monitor to validate timing aspects of the deadline
scheduler, as it works for tasks and servers, it's inclusion
requires also per-object monitors (for dl entities).
Note: this submission removed all patches depending on enqueue/dequeue
tracepoints. The current series is ready for inclusion while the other
patches will be submitted separately.
The entire series can also be found on:
git.kernel.org/pub/scm/linux/kernel/git/gmonaco/linux.git rv_hybrid_automata
Changes since V7:
* Fix wrong title in monitor_deadline docs
* Warn if kallsyms lookup fails
* Minor improvement in hybrid automata docs
* Use u8 instead of uint8_t in deadline tracepoints and models
* Drop enqueue/dequeue tracepoints and dependent patches from the
series, will submit separately
[1] - https://lore.kernel.org/lkml/20260310105627.332044-1-gmonaco@redhat.com
To: Steven Rostedt <rostedt@goodmis.org>
To: Nam Cao <namcao@linutronix.de>
To: Juri Lelli <jlelli@redhat.com>
Cc: Tomas Glozar <tglozar@redhat.com>
Cc: Clark Williams <williams@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: linux-trace-kernel@vger.kernel.org
Gabriele Monaco (12):
rv: Unify DA event handling functions across monitor types
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 monitor stall
rv: Convert the opid monitor to a hybrid automaton
rv: Add support for per-object monitors in DA/HA
verification/rvgen: Add support for per-obj monitors
sched: Add deadline tracepoints
sched/deadline: Move some utility functions to deadline.h
rv: Add nomiss deadline monitor
Documentation/tools/rv/index.rst | 1 +
Documentation/tools/rv/rv-mon-stall.rst | 44 ++
.../trace/rv/deterministic_automata.rst | 2 +-
Documentation/trace/rv/hybrid_automata.rst | 341 ++++++++++
Documentation/trace/rv/index.rst | 3 +
Documentation/trace/rv/monitor_deadline.rst | 84 +++
Documentation/trace/rv/monitor_sched.rst | 62 +-
Documentation/trace/rv/monitor_stall.rst | 43 ++
Documentation/trace/rv/monitor_synthesis.rst | 117 +++-
include/linux/rv.h | 39 ++
include/linux/sched/deadline.h | 27 +
include/rv/da_monitor.h | 644 +++++++++++++-----
include/rv/ha_monitor.h | 478 +++++++++++++
include/trace/events/sched.h | 26 +
kernel/sched/core.c | 5 +
kernel/sched/deadline.c | 51 +-
kernel/trace/rv/Kconfig | 18 +
kernel/trace/rv/Makefile | 3 +
kernel/trace/rv/monitors/deadline/Kconfig | 10 +
kernel/trace/rv/monitors/deadline/deadline.c | 44 ++
kernel/trace/rv/monitors/deadline/deadline.h | 202 ++++++
kernel/trace/rv/monitors/nomiss/Kconfig | 15 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 293 ++++++++
kernel/trace/rv/monitors/nomiss/nomiss.h | 123 ++++
.../trace/rv/monitors/nomiss/nomiss_trace.h | 19 +
kernel/trace/rv/monitors/opid/Kconfig | 11 +-
kernel/trace/rv/monitors/opid/opid.c | 111 +--
kernel/trace/rv/monitors/opid/opid.h | 86 +--
kernel/trace/rv/monitors/opid/opid_trace.h | 4 +
kernel/trace/rv/monitors/stall/Kconfig | 13 +
kernel/trace/rv/monitors/stall/stall.c | 150 ++++
kernel/trace/rv/monitors/stall/stall.h | 81 +++
kernel/trace/rv/monitors/stall/stall_trace.h | 19 +
kernel/trace/rv/rv_trace.h | 67 +-
tools/verification/models/deadline/nomiss.dot | 41 ++
tools/verification/models/sched/opid.dot | 36 +-
tools/verification/models/stall.dot | 22 +
tools/verification/rvgen/__main__.py | 8 +-
tools/verification/rvgen/rvgen/automata.py | 151 +++-
tools/verification/rvgen/rvgen/dot2c.py | 47 ++
tools/verification/rvgen/rvgen/dot2k.py | 489 ++++++++++++-
tools/verification/rvgen/rvgen/generator.py | 4 +-
.../rvgen/rvgen/templates/dot2k/main.c | 2 +-
.../rvgen/templates/dot2k/trace_hybrid.h | 16 +
44 files changed, 3605 insertions(+), 447 deletions(-)
create mode 100644 Documentation/tools/rv/rv-mon-stall.rst
create mode 100644 Documentation/trace/rv/hybrid_automata.rst
create mode 100644 Documentation/trace/rv/monitor_deadline.rst
create mode 100644 Documentation/trace/rv/monitor_stall.rst
create mode 100644 include/rv/ha_monitor.h
create mode 100644 kernel/trace/rv/monitors/deadline/Kconfig
create mode 100644 kernel/trace/rv/monitors/deadline/deadline.c
create mode 100644 kernel/trace/rv/monitors/deadline/deadline.h
create mode 100644 kernel/trace/rv/monitors/nomiss/Kconfig
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.c
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss.h
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_trace.h
create mode 100644 kernel/trace/rv/monitors/stall/Kconfig
create mode 100644 kernel/trace/rv/monitors/stall/stall.c
create mode 100644 kernel/trace/rv/monitors/stall/stall.h
create mode 100644 kernel/trace/rv/monitors/stall/stall_trace.h
create mode 100644 tools/verification/models/deadline/nomiss.dot
create mode 100644 tools/verification/models/stall.dot
create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
base-commit: 7aaa8047eafd0bd628065b15757d9b48c5f9c07d
--
2.53.0
^ permalink raw reply
* [PATCH v8 01/12] rv: Unify DA event handling functions across monitor types
From: Gabriele Monaco @ 2026-03-30 11:09 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli,
Gabriele Monaco, linux-trace-kernel
Cc: Tomas Glozar, Clark Williams, John Kacur
In-Reply-To: <20260330111010.153663-1-gmonaco@redhat.com>
The DA event handling functions are mostly duplicated because the
per-task monitors need to propagate the task struct while others do not.
Unify the functions, handle the difference by always passing an
identifier which is the task's pid for per-task monitors but is ignored
for the other types. Only keep the actual tracepoint calling separated.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 303 +++++++++++++++++-----------------------
1 file changed, 132 insertions(+), 171 deletions(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 7511f5464c48..89a0b81d4b3e 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -28,6 +28,13 @@
static struct rv_monitor rv_this;
+/*
+ * Type for the target id, default to int but can be overridden.
+ */
+#ifndef da_id_type
+#define da_id_type int
+#endif
+
static void react(enum states curr_state, enum events event)
{
rv_react(&rv_this,
@@ -97,90 +104,6 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
return 1;
}
-#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
-/*
- * Event handler for implicit monitors. Implicit monitor is the one which the
- * handler does not need to specify which da_monitor to manipulate. Examples
- * of implicit monitor are the per_cpu or the global ones.
- *
- * Retry in case there is a race between getting and setting the next state,
- * warn and reset the monitor if it runs out of retries. The monitor should be
- * able to handle various orders.
- */
-
-static inline bool da_event(struct da_monitor *da_mon, enum events event)
-{
- enum states curr_state, next_state;
-
- curr_state = READ_ONCE(da_mon->curr_state);
- for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
- next_state = model_get_next_state(curr_state, event);
- if (next_state == INVALID_STATE) {
- react(curr_state, event);
- CONCATENATE(trace_error_, MONITOR_NAME)(
- model_get_state_name(curr_state),
- model_get_event_name(event));
- return false;
- }
- if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
- CONCATENATE(trace_event_, MONITOR_NAME)(
- model_get_state_name(curr_state),
- model_get_event_name(event),
- model_get_state_name(next_state),
- model_is_final_state(next_state));
- return true;
- }
- }
-
- trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
- pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
- " retries reached for event %s, resetting monitor %s",
- model_get_event_name(event), __stringify(MONITOR_NAME));
- return false;
-}
-
-#elif RV_MON_TYPE == RV_MON_PER_TASK
-/*
- * Event handler for per_task monitors.
- *
- * Retry in case there is a race between getting and setting the next state,
- * warn and reset the monitor if it runs out of retries. The monitor should be
- * able to handle various orders.
- */
-
-static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk,
- enum events event)
-{
- enum states curr_state, next_state;
-
- curr_state = READ_ONCE(da_mon->curr_state);
- for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) {
- next_state = model_get_next_state(curr_state, event);
- if (next_state == INVALID_STATE) {
- react(curr_state, event);
- CONCATENATE(trace_error_, MONITOR_NAME)(tsk->pid,
- model_get_state_name(curr_state),
- model_get_event_name(event));
- return false;
- }
- if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
- CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid,
- model_get_state_name(curr_state),
- model_get_event_name(event),
- model_get_state_name(next_state),
- model_is_final_state(next_state));
- return true;
- }
- }
-
- trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
- pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
- " retries reached for event %s, resetting monitor %s",
- model_get_event_name(event), __stringify(MONITOR_NAME));
- return false;
-}
-#endif /* RV_MON_TYPE */
-
#if RV_MON_TYPE == RV_MON_GLOBAL
/*
* Functions to define, init and get a global monitor.
@@ -335,115 +258,179 @@ static inline void da_monitor_destroy(void)
#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
/*
- * Handle event for implicit monitor: da_get_monitor() will figure out
- * the monitor.
+ * Trace events for implicit monitors. Implicit monitor is the one which the
+ * handler does not need to specify which da_monitor to manipulate. Examples
+ * of implicit monitor are the per_cpu or the global ones.
*/
-static inline void __da_handle_event(struct da_monitor *da_mon,
- enum events event)
+static inline void da_trace_event(struct da_monitor *da_mon,
+ char *curr_state, char *event,
+ char *next_state, bool is_final,
+ da_id_type id)
{
- bool retval;
+ CONCATENATE(trace_event_, MONITOR_NAME)(curr_state, event, next_state,
+ is_final);
+}
- retval = da_event(da_mon, event);
- if (!retval)
- da_monitor_reset(da_mon);
+static inline void da_trace_error(struct da_monitor *da_mon,
+ char *curr_state, char *event,
+ da_id_type id)
+{
+ CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
}
+#elif RV_MON_TYPE == RV_MON_PER_TASK
/*
- * da_handle_event - handle an event
+ * Trace events for per_task monitors, report the PID of the task.
*/
-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;
+static inline void da_trace_event(struct da_monitor *da_mon,
+ char *curr_state, char *event,
+ char *next_state, bool is_final,
+ da_id_type id)
+{
+ CONCATENATE(trace_event_, MONITOR_NAME)(id, curr_state, event,
+ next_state, is_final);
+}
- __da_handle_event(da_mon, event);
+static inline void da_trace_error(struct da_monitor *da_mon,
+ char *curr_state, char *event,
+ da_id_type id)
+{
+ CONCATENATE(trace_error_, MONITOR_NAME)(id, curr_state, event);
}
+#endif /* RV_MON_TYPE */
/*
- * da_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:
+ * da_event - handle an event for the da_mon
*
- * If the monitor already started, handle the event.
- * If the monitor did not start yet, start the monitor but skip the event.
+ * This function is valid for both implicit and id monitors.
+ * Retry in case there is a race between getting and setting the next state,
+ * warn and reset the monitor if it runs out of retries. The monitor should be
+ * able to handle various orders.
*/
-static inline bool da_handle_start_event(enum events event)
+static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_type id)
{
- struct da_monitor *da_mon;
+ enum states curr_state, next_state;
- if (!da_monitor_enabled())
- return 0;
+ 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) {
+ react(curr_state, event);
+ da_trace_error(da_mon, model_get_state_name(curr_state),
+ model_get_event_name(event), id);
+ return false;
+ }
+ if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+ da_trace_event(da_mon, model_get_state_name(curr_state),
+ model_get_event_name(event),
+ model_get_state_name(next_state),
+ model_is_final_state(next_state), id);
+ return true;
+ }
+ }
+
+ trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event));
+ pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS)
+ " retries reached for event %s, resetting monitor %s",
+ model_get_event_name(event), __stringify(MONITOR_NAME));
+ return false;
+}
- da_mon = da_get_monitor();
+static inline void __da_handle_event_common(struct da_monitor *da_mon,
+ enum events event, da_id_type id)
+{
+ if (!da_event(da_mon, event, id))
+ da_monitor_reset(da_mon);
+}
+static inline void __da_handle_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id)
+{
+ if (da_monitor_handling_event(da_mon))
+ __da_handle_event_common(da_mon, event, id);
+}
+
+static inline bool __da_handle_start_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id)
+{
+ if (!da_monitor_enabled())
+ return 0;
if (unlikely(!da_monitoring(da_mon))) {
da_monitor_start(da_mon);
return 0;
}
- __da_handle_event(da_mon, event);
+ __da_handle_event_common(da_mon, event, id);
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)
+static inline bool __da_handle_start_run_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id)
{
- 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);
+ __da_handle_event_common(da_mon, event, id);
return 1;
}
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
/*
- * Handle event for per task.
+ * Handle event for implicit monitor: da_get_monitor() will figure out
+ * the monitor.
*/
-static inline void __da_handle_event(struct da_monitor *da_mon,
- struct task_struct *tsk, enum events event)
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event(enum events event)
{
- bool retval;
+ __da_handle_event(da_get_monitor(), event, 0);
+}
- retval = da_event(da_mon, tsk, event);
- if (!retval)
- da_monitor_reset(da_mon);
+/*
+ * 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)
+{
+ return __da_handle_start_event(da_get_monitor(), event, 0);
}
/*
- * da_handle_event - handle an event
+ * 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 void da_handle_event(struct task_struct *tsk, enum events event)
+static inline bool da_handle_start_run_event(enum events event)
{
- struct da_monitor *da_mon = da_get_monitor(tsk);
- bool retval;
+ return __da_handle_start_run_event(da_get_monitor(), event, 0);
+}
- retval = da_monitor_handling_event(da_mon);
- if (!retval)
- return;
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * Handle event for per task.
+ */
- __da_handle_event(da_mon, tsk, event);
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event(struct task_struct *tsk, enum events event)
+{
+ __da_handle_event(da_get_monitor(tsk), event, tsk->pid);
}
/*
@@ -459,21 +446,7 @@ static inline void da_handle_event(struct task_struct *tsk, enum events event)
static inline bool da_handle_start_event(struct task_struct *tsk,
enum events event)
{
- struct da_monitor *da_mon;
-
- if (!da_monitor_enabled())
- return 0;
-
- da_mon = da_get_monitor(tsk);
-
- if (unlikely(!da_monitoring(da_mon))) {
- da_monitor_start(da_mon);
- return 0;
- }
-
- __da_handle_event(da_mon, tsk, event);
-
- return 1;
+ return __da_handle_start_event(da_get_monitor(tsk), event, tsk->pid);
}
/*
@@ -485,19 +458,7 @@ static inline bool da_handle_start_event(struct task_struct *tsk,
static inline bool da_handle_start_run_event(struct task_struct *tsk,
enum events event)
{
- struct da_monitor *da_mon;
-
- if (!da_monitor_enabled())
- return 0;
-
- da_mon = da_get_monitor(tsk);
-
- if (unlikely(!da_monitoring(da_mon)))
- da_monitor_start(da_mon);
-
- __da_handle_event(da_mon, tsk, event);
-
- return 1;
+ return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid);
}
#endif /* RV_MON_TYPE */
--
2.53.0
^ permalink raw reply related
* [PATCH v8 02/12] rv: Add Hybrid Automata monitor type
From: Gabriele Monaco @ 2026-03-30 11:10 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli,
Gabriele Monaco, Masami Hiramatsu, linux-trace-kernel
Cc: Tomas Glozar, Clark Williams, John Kacur
In-Reply-To: <20260330111010.153663-1-gmonaco@redhat.com>
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.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/linux/rv.h | 38 +++
include/rv/da_monitor.h | 73 +++++-
include/rv/ha_monitor.h | 475 +++++++++++++++++++++++++++++++++++++
kernel/trace/rv/Kconfig | 13 +
kernel/trace/rv/rv_trace.h | 63 +++++
5 files changed, 658 insertions(+), 4 deletions(-)
create mode 100644 include/rv/ha_monitor.h
diff --git a/include/linux/rv.h b/include/linux/rv.h
index 58774eb3aecf..0aef9e3c785c 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -81,11 +81,49 @@ struct ltl_monitor {};
#endif /* CONFIG_RV_LTL_MONITOR */
+#ifdef CONFIG_RV_HA_MONITOR
+/*
+ * In the future, hybrid automata may rely on multiple
+ * environment variables, e.g. different clocks started at
+ * different times or running at different speed.
+ * For now we support only 1 variable.
+ */
+#define MAX_HA_ENV_LEN 1
+
+/*
+ * Monitors can pick the preferred timer implementation:
+ * No timer: if monitors don't have state invariants.
+ * Timer wheel: lightweight invariants check but far less precise.
+ * Hrtimer: accurate invariants check with higher overhead.
+ */
+#define HA_TIMER_NONE 0
+#define HA_TIMER_WHEEL 1
+#define HA_TIMER_HRTIMER 2
+
+/*
+ * Hybrid automaton per-object variables.
+ */
+struct ha_monitor {
+ struct da_monitor da_mon;
+ u64 env_store[MAX_HA_ENV_LEN];
+ union {
+ struct hrtimer hrtimer;
+ struct timer_list timer;
+ };
+};
+
+#else
+
+struct ha_monitor { };
+
+#endif /* CONFIG_RV_HA_MONITOR */
+
#define RV_PER_TASK_MONITOR_INIT (CONFIG_RV_PER_TASK_MONITORS)
union rv_task_monitor {
struct da_monitor da_mon;
struct ltl_monitor ltl_mon;
+ struct ha_monitor ha_mon;
};
#ifdef CONFIG_RV_REACTORS
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 89a0b81d4b3e..ab5fe0896a46 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -3,9 +3,9 @@
* Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
*
* Deterministic automata (DA) monitor functions, to be used together
- * with automata models in C generated by the dot2k tool.
+ * with automata models in C generated by the rvgen tool.
*
- * The dot2k tool is available at tools/verification/dot2k/
+ * The rvgen tool is available at tools/verification/rvgen/
*
* For further information, see:
* Documentation/trace/rv/monitor_synthesis.rst
@@ -28,6 +28,33 @@
static struct rv_monitor rv_this;
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes curr_state, event and next_state and returns true if the
+ * environment constraints (e.g. timing) are satisfied, false otherwise.
+ */
+#ifndef da_monitor_event_hook
+#define da_monitor_event_hook(...) true
+#endif
+
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes the da_monitor and performs further initialisation
+ * (e.g. reset set up timers).
+ */
+#ifndef da_monitor_init_hook
+#define da_monitor_init_hook(da_mon)
+#endif
+
+/*
+ * Hook to allow the implementation of hybrid automata: define it with a
+ * function that takes the da_monitor and performs further reset (e.g. reset
+ * all clocks).
+ */
+#ifndef da_monitor_reset_hook
+#define da_monitor_reset_hook(da_mon)
+#endif
+
/*
* Type for the target id, default to int but can be overridden.
*/
@@ -49,6 +76,7 @@ static void react(enum states curr_state, enum events event)
*/
static inline void da_monitor_reset(struct da_monitor *da_mon)
{
+ da_monitor_reset_hook(da_mon);
da_mon->monitoring = 0;
da_mon->curr_state = model_get_initial_state();
}
@@ -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);
}
/*
@@ -142,7 +171,10 @@ static inline int da_monitor_init(void)
/*
* da_monitor_destroy - destroy the monitor
*/
-static inline void da_monitor_destroy(void) { }
+static inline void da_monitor_destroy(void)
+{
+ da_monitor_reset_all();
+}
#elif RV_MON_TYPE == RV_MON_PER_CPU
/*
@@ -188,7 +220,10 @@ static inline int da_monitor_init(void)
/*
* da_monitor_destroy - destroy the monitor
*/
-static inline void da_monitor_destroy(void) { }
+static inline void da_monitor_destroy(void)
+{
+ da_monitor_reset_all();
+}
#elif RV_MON_TYPE == RV_MON_PER_TASK
/*
@@ -209,6 +244,24 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
return &tsk->rv[task_mon_slot].da_mon;
}
+/*
+ * da_get_task - return the task associated to the monitor
+ */
+static inline struct task_struct *da_get_task(struct da_monitor *da_mon)
+{
+ return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon);
+}
+
+/*
+ * da_get_id - return the id associated to the monitor
+ *
+ * For per-task monitors, the id is the task's PID.
+ */
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+ return da_get_task(da_mon)->pid;
+}
+
static void da_monitor_reset_all(void)
{
struct task_struct *g, *p;
@@ -253,6 +306,8 @@ static inline void da_monitor_destroy(void)
}
rv_put_task_monitor_slot(task_mon_slot);
task_mon_slot = RV_PER_TASK_MONITOR_INIT;
+
+ da_monitor_reset_all();
}
#endif /* RV_MON_TYPE */
@@ -279,6 +334,14 @@ static inline void da_trace_error(struct da_monitor *da_mon,
CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
}
+/*
+ * da_get_id - unused for implicit monitors
+ */
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+ return 0;
+}
+
#elif RV_MON_TYPE == RV_MON_PER_TASK
/*
* Trace events for per_task monitors, report the PID of the task.
@@ -323,6 +386,8 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_
return false;
}
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
+ if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
+ return false;
da_trace_event(da_mon, model_get_state_name(curr_state),
model_get_event_name(event),
model_get_state_name(next_state),
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
new file mode 100644
index 000000000000..b6cf3b2ba989
--- /dev/null
+++ b/include/rv/ha_monitor.h
@@ -0,0 +1,475 @@
+/* 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 rvgen 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 rvgen tool is available at tools/verification/rvgen/
+ *
+ * For further information, see:
+ * Documentation/trace/rv/monitor_synthesis.rst
+ */
+
+#ifndef _RV_HA_MONITOR_H
+#define _RV_HA_MONITOR_H
+
+#include <rv/automata.h>
+
+#ifndef da_id_type
+#define da_id_type int
+#endif
+
+static inline void ha_monitor_init_env(struct da_monitor *da_mon);
+static inline void ha_monitor_reset_env(struct da_monitor *da_mon);
+static inline void ha_setup_timer(struct ha_monitor *ha_mon);
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon);
+static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
+ enum states curr_state,
+ enum events event,
+ enum states next_state,
+ da_id_type id);
+#define da_monitor_event_hook ha_monitor_handle_constraint
+#define da_monitor_init_hook ha_monitor_init_env
+#define da_monitor_reset_hook ha_monitor_reset_env
+
+#include <rv/da_monitor.h>
+#include <linux/seq_buf.h>
+
+/* This simplifies things since da_mon and ha_mon coexist in the same union */
+_Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
+ "da_mon must be the first element in an ha_mon!");
+#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon)
+
+#define ENV_MAX CONCATENATE(env_max_, MONITOR_NAME)
+#define ENV_MAX_STORED CONCATENATE(env_max_stored_, MONITOR_NAME)
+#define envs CONCATENATE(envs_, MONITOR_NAME)
+
+/* Environment storage before being reset */
+#define ENV_INVALID_VALUE U64_MAX
+/* Error with no event occurs only on timeouts */
+#define EVENT_NONE EVENT_MAX
+#define EVENT_NONE_LBL "none"
+#define ENV_BUFFER_SIZE 64
+
+#ifdef CONFIG_RV_REACTORS
+
+/*
+ * ha_react - trigger the reaction after a failed environment constraint
+ *
+ * The transition from curr_state with event is otherwise valid, but the
+ * environment constraint is false. This function can be called also with no
+ * event from a timer (state constraints only).
+ */
+static void ha_react(enum states curr_state, enum events event, char *env)
+{
+ rv_react(&rv_this,
+ "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_react(enum states curr_state, enum events event, char *env) { }
+#endif
+
+/*
+ * model_get_state_name - return the (string) name of the given state
+ */
+static char *model_get_env_name(enum envs env)
+{
+ if ((env < 0) || (env >= ENV_MAX))
+ return "INVALID";
+
+ return RV_AUTOMATON_NAME.env_names[env];
+}
+
+/*
+ * Monitors requiring a timer implementation need to request it explicitly.
+ */
+#ifndef HA_TIMER_TYPE
+#define HA_TIMER_TYPE HA_TIMER_NONE
+#endif
+
+#if HA_TIMER_TYPE == HA_TIMER_WHEEL
+static void ha_monitor_timer_callback(struct timer_list *timer);
+#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer);
+#endif
+
+/*
+ * ktime_get_ns is expensive, since we usually don't require precise accounting
+ * of changes within the same event, cache the current time at the beginning of
+ * the constraint handler and use the cache for subsequent calls.
+ * Monitors without ns clocks automatically skip this.
+ */
+#ifdef HA_CLK_NS
+#define ha_get_ns() ktime_get_ns()
+#else
+#define ha_get_ns() 0
+#endif /* HA_CLK_NS */
+
+/* Should be supplied by the monitor */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state,
+ enum events event,
+ enum states next_state,
+ u64 time_ns);
+
+/*
+ * ha_monitor_reset_all_stored - reset all environment variables in the monitor
+ */
+static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon)
+{
+ for (int i = 0; i < ENV_MAX_STORED; i++)
+ WRITE_ONCE(ha_mon->env_store[i], ENV_INVALID_VALUE);
+}
+
+/*
+ * ha_monitor_init_env - setup timer and reset all environment
+ *
+ * Called from a hook in the DA start functions, it supplies the da_mon
+ * corresponding to the current ha_mon.
+ * Not all hybrid automata require the timer, still set it for simplicity.
+ */
+static inline void ha_monitor_init_env(struct da_monitor *da_mon)
+{
+ struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+
+ ha_monitor_reset_all_stored(ha_mon);
+ ha_setup_timer(ha_mon);
+}
+
+/*
+ * ha_monitor_reset_env - stop timer and reset all environment
+ *
+ * Called from a hook in the DA reset functions, it supplies the da_mon
+ * corresponding to the current ha_mon.
+ * Not all hybrid automata require the timer, still clear it for simplicity.
+ */
+static inline void ha_monitor_reset_env(struct da_monitor *da_mon)
+{
+ struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+
+ /* Initialisation resets the monitor before initialising the timer */
+ if (likely(da_monitoring(da_mon)))
+ ha_cancel_timer(ha_mon);
+}
+
+/*
+ * ha_monitor_env_invalid - return true if env has not been initialised
+ */
+static inline bool ha_monitor_env_invalid(struct ha_monitor *ha_mon, enum envs env)
+{
+ return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE;
+}
+
+static inline void ha_get_env_string(struct seq_buf *s,
+ struct ha_monitor *ha_mon, u64 time_ns)
+{
+ const char *format_str = "%s=%llu";
+
+ for (int i = 0; i < ENV_MAX; i++) {
+ seq_buf_printf(s, format_str, model_get_env_name(i),
+ ha_get_env(ha_mon, i, time_ns));
+ format_str = ",%s=%llu";
+ }
+}
+
+#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU
+static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
+ char *curr_state, char *event, char *env,
+ da_id_type id)
+{
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(curr_state, event, env);
+}
+#elif RV_MON_TYPE == RV_MON_PER_TASK
+static inline void ha_trace_error_env(struct ha_monitor *ha_mon,
+ char *curr_state, char *event, char *env,
+ da_id_type id)
+{
+ CONCATENATE(trace_error_env_, MONITOR_NAME)(id, curr_state, event, env);
+}
+#endif /* RV_MON_TYPE */
+
+/*
+ * ha_get_monitor - return the current monitor
+ */
+#define ha_get_monitor(...) to_ha_monitor(da_get_monitor(__VA_ARGS__))
+
+/*
+ * ha_monitor_handle_constraint - handle the constraint on the current transition
+ *
+ * If the monitor implementation defines a constraint in the transition from
+ * curr_state to event, react and trace appropriately as well as return false.
+ * This function is called from the hook in the DA event handle function and
+ * triggers a failure in the monitor.
+ */
+static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
+ enum states curr_state,
+ enum events event,
+ enum states next_state,
+ da_id_type id)
+{
+ struct ha_monitor *ha_mon = to_ha_monitor(da_mon);
+ u64 time_ns = ha_get_ns();
+ DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE);
+
+ if (ha_verify_constraint(ha_mon, curr_state, event, next_state, time_ns))
+ return true;
+
+ ha_get_env_string(&env_string, ha_mon, time_ns);
+ ha_react(curr_state, event, env_string.buffer);
+ ha_trace_error_env(ha_mon,
+ model_get_state_name(curr_state),
+ model_get_event_name(event),
+ env_string.buffer, id);
+ return false;
+}
+
+static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon)
+{
+ enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state);
+ DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE);
+ u64 time_ns = ha_get_ns();
+
+ ha_get_env_string(&env_string, ha_mon, time_ns);
+ ha_react(curr_state, EVENT_NONE, env_string.buffer);
+ ha_trace_error_env(ha_mon, model_get_state_name(curr_state),
+ EVENT_NONE_LBL, env_string.buffer,
+ da_get_id(&ha_mon->da_mon));
+
+ da_monitor_reset(&ha_mon->da_mon);
+}
+
+/*
+ * The clock variables have 2 different representations in the env_store:
+ * - The guard representation is the timestamp of the last reset
+ * - The invariant representation is the timestamp when the invariant expires
+ * As the representations are incompatible, care must be taken when switching
+ * between them: the invariant representation can only be used when starting a
+ * timer when the previous representation was guard (e.g. no other invariant
+ * started since the last reset operation).
+ * Likewise, switching from invariant to guard representation without a reset
+ * can be done only by subtracting the exact value used to start the invariant.
+ *
+ * Reading the environment variable (ha_get_clk) also reflects this difference
+ * any reads in states that have an invariant return the (possibly negative)
+ * time since expiration, other reads return the time since last reset.
+ */
+
+/*
+ * Helper functions for env variables describing clocks with ns granularity
+ */
+static inline u64 ha_get_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
+{
+ return time_ns - READ_ONCE(ha_mon->env_store[env]);
+}
+static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns)
+{
+ WRITE_ONCE(ha_mon->env_store[env], time_ns);
+}
+static inline void ha_set_invariant_ns(struct ha_monitor *ha_mon, enum envs env,
+ u64 value, u64 time_ns)
+{
+ WRITE_ONCE(ha_mon->env_store[env], time_ns + value);
+}
+static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon,
+ enum envs env, u64 time_ns)
+{
+ return READ_ONCE(ha_mon->env_store[env]) >= time_ns;
+}
+/*
+ * ha_invariant_passed_ns - prepare the invariant and return the time since reset
+ */
+static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ u64 passed = 0;
+
+ if (env < 0 || env >= ENV_MAX_STORED)
+ return 0;
+ if (ha_monitor_env_invalid(ha_mon, env))
+ return 0;
+ passed = ha_get_env(ha_mon, env, time_ns);
+ ha_set_invariant_ns(ha_mon, env, expire - passed, time_ns);
+ return passed;
+}
+
+/*
+ * Helper functions for env variables describing clocks with jiffy granularity
+ */
+static inline u64 ha_get_clk_jiffy(struct ha_monitor *ha_mon, enum envs env)
+{
+ return get_jiffies_64() - READ_ONCE(ha_mon->env_store[env]);
+}
+static inline void ha_reset_clk_jiffy(struct ha_monitor *ha_mon, enum envs env)
+{
+ WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64());
+}
+static inline void ha_set_invariant_jiffy(struct ha_monitor *ha_mon,
+ enum envs env, u64 value)
+{
+ WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64() + value);
+}
+static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon,
+ enum envs env, u64 time_ns)
+{
+ return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64());
+
+}
+/*
+ * ha_invariant_passed_jiffy - prepare the invariant and return the time since reset
+ */
+static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ u64 passed = 0;
+
+ if (env < 0 || env >= ENV_MAX_STORED)
+ return 0;
+ if (ha_monitor_env_invalid(ha_mon, env))
+ return 0;
+ passed = ha_get_env(ha_mon, env, time_ns);
+ ha_set_invariant_jiffy(ha_mon, env, expire - passed);
+ return passed;
+}
+
+/*
+ * Retrieve the last reset time (guard representation) from the invariant
+ * representation (expiration).
+ * It the caller's responsibility to make sure the storage was actually in the
+ * invariant representation (e.g. the current state has an invariant).
+ * The provided value must be the same used when starting the invariant.
+ *
+ * This function's access to the storage is NOT atomic, due to the rarity when
+ * this is used. If a monitor allows writes concurrent to this, likely
+ * other things are broken and need rethinking the model or additional locking.
+ */
+static inline void ha_inv_to_guard(struct ha_monitor *ha_mon, enum envs env,
+ u64 value, u64 time_ns)
+{
+ WRITE_ONCE(ha_mon->env_store[env], READ_ONCE(ha_mon->env_store[env]) - value);
+}
+
+#if HA_TIMER_TYPE == HA_TIMER_WHEEL
+/*
+ * Helper functions to handle the monitor timer.
+ * Not all monitors require a timer, in such case the timer will be set up but
+ * never armed.
+ * Timers start since the last reset of the supplied env or from now if env is
+ * not an environment variable. If env was not initialised no timer starts.
+ * Timers can expire on any CPU unless the monitor is per-cpu,
+ * where we assume every event occurs on the local CPU.
+ */
+static void ha_monitor_timer_callback(struct timer_list *timer)
+{
+ struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer);
+
+ __ha_monitor_timer_callback(ha_mon);
+}
+static inline void ha_setup_timer(struct ha_monitor *ha_mon)
+{
+ int mode = 0;
+
+ if (RV_MON_TYPE == RV_MON_PER_CPU)
+ mode |= TIMER_PINNED;
+ timer_setup(&ha_mon->timer, ha_monitor_timer_callback, mode);
+}
+static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns);
+
+ mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
+}
+static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns);
+
+ ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
+ nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns);
+}
+/*
+ * ha_cancel_timer - Cancel the timer
+ *
+ * Returns:
+ * * 1 when the timer was active
+ * * 0 when the timer was not active or running a callback
+ */
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+ return timer_delete(&ha_mon->timer);
+}
+#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER
+/*
+ * Helper functions to handle the monitor timer.
+ * Not all monitors require a timer, in such case the timer will be set up but
+ * never armed.
+ * Timers start since the last reset of the supplied env or from now if env is
+ * not an environment variable. If env was not initialised no timer starts.
+ * Timers can expire on any CPU unless the monitor is per-cpu,
+ * where we assume every event occurs on the local CPU.
+ */
+static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer)
+{
+ struct ha_monitor *ha_mon = container_of(hrtimer, struct ha_monitor, hrtimer);
+
+ __ha_monitor_timer_callback(ha_mon);
+ return HRTIMER_NORESTART;
+}
+static inline void ha_setup_timer(struct ha_monitor *ha_mon)
+{
+ hrtimer_setup(&ha_mon->hrtimer, ha_monitor_timer_callback,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
+}
+static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ int mode = HRTIMER_MODE_REL_HARD;
+ u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns);
+
+ if (RV_MON_TYPE == RV_MON_PER_CPU)
+ mode |= HRTIMER_MODE_PINNED;
+ hrtimer_start(&ha_mon->hrtimer, ns_to_ktime(expire - passed), mode);
+}
+static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
+ u64 expire, u64 time_ns)
+{
+ u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns);
+
+ ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
+ jiffies_to_nsecs(expire - passed), time_ns);
+}
+/*
+ * ha_cancel_timer - Cancel the timer
+ *
+ * Returns:
+ * * 1 when the timer was active
+ * * 0 when the timer was not active or running a callback
+ */
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+ return hrtimer_try_to_cancel(&ha_mon->hrtimer) == 1;
+}
+#else /* HA_TIMER_NONE */
+/*
+ * Start function is intentionally not defined, monitors using timers must
+ * set HA_TIMER_TYPE to either HA_TIMER_WHEEL or HA_TIMER_HRTIMER.
+ */
+static inline void ha_setup_timer(struct ha_monitor *ha_mon) { }
+static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
+{
+ return false;
+}
+#endif
+
+#endif
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 5b4be87ba59d..4ad392dfc57f 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -23,6 +23,19 @@ config LTL_MON_EVENTS_ID
config RV_LTL_MONITOR
bool
+config RV_HA_MONITOR
+ bool
+
+config HA_MON_EVENTS_IMPLICIT
+ select DA_MON_EVENTS_IMPLICIT
+ select RV_HA_MONITOR
+ bool
+
+config HA_MON_EVENTS_ID
+ select DA_MON_EVENTS_ID
+ select RV_HA_MONITOR
+ bool
+
menuconfig RV
bool "Runtime Verification"
select TRACING
diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h
index 4a6faddac614..7c598967bc0e 100644
--- a/kernel/trace/rv/rv_trace.h
+++ b/kernel/trace/rv/rv_trace.h
@@ -65,6 +65,36 @@ DECLARE_EVENT_CLASS(error_da_monitor,
#include <monitors/opid/opid_trace.h>
// Add new monitors based on CONFIG_DA_MON_EVENTS_IMPLICIT here
+#ifdef CONFIG_HA_MON_EVENTS_IMPLICIT
+/* For simplicity this class is marked as DA although relevant only for HA */
+DECLARE_EVENT_CLASS(error_env_da_monitor,
+
+ TP_PROTO(char *state, char *event, char *env),
+
+ TP_ARGS(state, event, env),
+
+ TP_STRUCT__entry(
+ __string( state, state )
+ __string( event, event )
+ __string( env, env )
+ ),
+
+ TP_fast_assign(
+ __assign_str(state);
+ __assign_str(event);
+ __assign_str(env);
+ ),
+
+ TP_printk("event %s not expected in the state %s with env %s",
+ __get_str(event),
+ __get_str(state),
+ __get_str(env))
+);
+
+// Add new monitors based on CONFIG_HA_MON_EVENTS_IMPLICIT here
+
+#endif
+
#endif /* CONFIG_DA_MON_EVENTS_IMPLICIT */
#ifdef CONFIG_DA_MON_EVENTS_ID
@@ -128,6 +158,39 @@ DECLARE_EVENT_CLASS(error_da_monitor_id,
#include <monitors/sssw/sssw_trace.h>
// Add new monitors based on CONFIG_DA_MON_EVENTS_ID here
+#ifdef CONFIG_HA_MON_EVENTS_ID
+/* For simplicity this class is marked as DA although relevant only for HA */
+DECLARE_EVENT_CLASS(error_env_da_monitor_id,
+
+ TP_PROTO(int id, char *state, char *event, char *env),
+
+ TP_ARGS(id, state, event, env),
+
+ TP_STRUCT__entry(
+ __field( int, id )
+ __string( state, state )
+ __string( event, event )
+ __string( env, env )
+ ),
+
+ TP_fast_assign(
+ __assign_str(state);
+ __assign_str(event);
+ __assign_str(env);
+ __entry->id = id;
+ ),
+
+ TP_printk("%d: event %s not expected in the state %s with env %s",
+ __entry->id,
+ __get_str(event),
+ __get_str(state),
+ __get_str(env))
+);
+
+// Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
+
+#endif
+
#endif /* CONFIG_DA_MON_EVENTS_ID */
#ifdef CONFIG_LTL_MON_EVENTS_ID
DECLARE_EVENT_CLASS(event_ltl_monitor_id,
--
2.53.0
^ permalink raw reply related
* [PATCH v8 03/12] verification/rvgen: Allow spaces in and events strings
From: Gabriele Monaco @ 2026-03-30 11:10 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli,
Gabriele Monaco, linux-trace-kernel
Cc: Tomas Glozar, Clark Williams, John Kacur
In-Reply-To: <20260330111010.153663-1-gmonaco@redhat.com>
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.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/rvgen/automata.py | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
index 3f06aef8d4fd..34a2e2a6b217 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.53.0
^ permalink raw reply related
* [PATCH v8 04/12] verification/rvgen: Add support for Hybrid Automata
From: Gabriele Monaco @ 2026-03-30 11:10 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli,
Gabriele Monaco, linux-trace-kernel
Cc: Tomas Glozar, Clark Williams, John Kacur
In-Reply-To: <20260330111010.153663-1-gmonaco@redhat.com>
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.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/__main__.py | 8 +-
tools/verification/rvgen/rvgen/automata.py | 146 +++++-
tools/verification/rvgen/rvgen/dot2c.py | 47 ++
tools/verification/rvgen/rvgen/dot2k.py | 474 +++++++++++++++++-
tools/verification/rvgen/rvgen/generator.py | 2 +
.../rvgen/rvgen/templates/dot2k/main.c | 2 +-
.../rvgen/templates/dot2k/trace_hybrid.h | 16 +
7 files changed, 679 insertions(+), 16 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 34a2e2a6b217..5c1c5597d839 100644
--- a/tools/verification/rvgen/rvgen/automata.py
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -9,24 +9,64 @@
# Documentation/trace/rv/deterministic_automata.rst
import ntpath
+import re
+from typing import Iterator
+
+class _ConstraintKey:
+ """Base class for constraint keys."""
+
+class _StateConstraintKey(_ConstraintKey, int):
+ """Key for a state constraint. Under the hood just state_id."""
+ def __new__(cls, state_id: int):
+ return super().__new__(cls, state_id)
+
+class _EventConstraintKey(_ConstraintKey, tuple):
+ """Key for an event constraint. Under the hood just tuple(state_id,event_id)."""
+ def __new__(cls, state_id: int, event_id: int):
+ return super().__new__(cls, (state_id, event_id))
class Automata:
"""Automata class: Reads a dot file and part it as an automata.
+ It supports both deterministic and hybrid automata.
+
Attributes:
dot_file: A dot file with an state_automaton definition.
"""
invalid_state_str = "INVALID_STATE"
+ # val can be numerical, uppercase (constant or macro), lowercase (parameter or function)
+ # only numerical values should have units
+ constraint_rule = re.compile(r"""
+ ^
+ (?P<env>[a-zA-Z_][a-zA-Z0-9_]+) # C-like identifier for the env var
+ (?P<op>[!<=>]{1,2}) # operator
+ (?P<val>
+ [0-9]+ | # numerical value
+ [A-Z_]+\(\) | # macro
+ [A-Z_]+ | # constant
+ [a-z_]+\(\) | # function
+ [a-z_]+ # parameter
+ )
+ (?P<unit>[a-z]{1,2})? # optional unit for numerical values
+ """, re.VERBOSE)
+ constraint_reset = re.compile(r"^reset\((?P<env>[a-zA-Z_][a-zA-Z0-9_]+)\)")
def __init__(self, file_path, model_name=None):
self.__dot_path = file_path
self.name = model_name or self.__get_model_name()
self.__dot_lines = self.__open_dot()
self.states, self.initial_state, self.final_states = self.__get_state_variables()
- self.events = self.__get_event_variables()
- self.function = self.__create_matrix()
+ self.env_types = {}
+ self.env_stored = set()
+ self.constraint_vars = set()
+ self.self_loop_reset_events = set()
+ self.events, self.envs = self.__get_event_variables()
+ self.function, self.constraints = self.__create_matrix()
self.events_start, self.events_start_run = self.__store_init_events()
+ self.env_stored = sorted(self.env_stored)
+ self.constraint_vars = sorted(self.constraint_vars)
+ self.self_loop_reset_events = sorted(self.self_loop_reset_events)
def __get_model_name(self) -> str:
basename = ntpath.basename(self.__dot_path)
@@ -116,30 +156,93 @@ class Automata:
return states, initial_state, final_states
- def __get_event_variables(self) -> list[str]:
+ def __get_event_variables(self) -> tuple[list[str], list[str]]:
# here we are at the begin of transitions, take a note, we will return later.
cursor = self.__get_cursor_begin_events()
events = []
+ envs = []
while self.__dot_lines[cursor].lstrip()[0] == '"':
# transitions have the format:
# "all_fired" -> "both_fired" [ label = "disable_irq" ];
# ------------ event is here ------------^^^^^
if self.__dot_lines[cursor].split()[1] == "->":
line = self.__dot_lines[cursor].split()
- event = "".join(line[line.index("label")+2:-1]).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.
for i in event.split("\\n"):
- events.append(i)
+ # if the event contains a constraint (hybrid automata),
+ # it will be separated by a ";":
+ # "sched_switch;x<1000;reset(x)"
+ ev, *constr = i.split(";")
+ if constr:
+ if len(constr) > 2:
+ raise ValueError("Only 1 constraint and 1 reset are supported")
+ envs += self.__extract_env_var(constr)
+ events.append(ev)
+ else:
+ # state labels have the format:
+ # "enable_fired" [label = "enable_fired\ncondition"];
+ # ----- label is here -----^^^^^
+ # label and node name must be the same, condition is optional
+ state = self.__dot_lines[cursor].split("label")[1].split('"')[1]
+ _, *constr = state.split("\\n")
+ if constr:
+ if len(constr) > 1:
+ raise ValueError("Only 1 constraint is supported in the state")
+ envs += self.__extract_env_var([constr[0].replace(" ", "")])
cursor += 1
- return sorted(set(events))
-
- def __create_matrix(self) -> list[list[str]]:
+ return sorted(set(events)), sorted(set(envs))
+
+ def _split_constraint_expr(self, constr: list[str]) -> Iterator[tuple[str,
+ str | None]]:
+ """
+ Get a list of strings of the type constr1 && constr2 and returns a list of
+ constraints and separators: [[constr1,"&&"],[constr2,None]]
+ """
+ exprs = []
+ seps = []
+ for c in constr:
+ while "&&" in c or "||" in c:
+ a = c.find("&&")
+ o = c.find("||")
+ pos = a if o < 0 or 0 < a < o else o
+ exprs.append(c[:pos].replace(" ", ""))
+ seps.append(c[pos:pos + 2].replace(" ", ""))
+ c = c[pos + 2:].replace(" ", "")
+ exprs.append(c)
+ seps.append(None)
+ return zip(exprs, seps)
+
+ def __extract_env_var(self, constraint: list[str]) -> list[str]:
+ env = []
+ for c, _ in self._split_constraint_expr(constraint):
+ rule = self.constraint_rule.search(c)
+ reset = self.constraint_reset.search(c)
+ if rule:
+ env.append(rule["env"])
+ if rule.groupdict().get("unit"):
+ self.env_types[rule["env"]] = rule["unit"]
+ if rule["val"][0].isalpha():
+ self.constraint_vars.add(rule["val"])
+ # try to infer unit from constants or parameters
+ val_for_unit = rule["val"].lower().replace("()", "")
+ if val_for_unit.endswith("_ns"):
+ self.env_types[rule["env"]] = "ns"
+ if val_for_unit.endswith("_jiffies"):
+ self.env_types[rule["env"]] = "j"
+ if reset:
+ env.append(reset["env"])
+ # environment variables that are reset need a storage
+ self.env_stored.add(reset["env"])
+ return env
+
+ def __create_matrix(self) -> tuple[list[list[str]], dict[_ConstraintKey, list[str]]]:
# transform the array into a dictionary
events = self.events
states = self.states
@@ -157,6 +260,7 @@ class Automata:
# declare the matrix....
matrix = [[ self.invalid_state_str for x in range(nr_event)] for y in range(nr_state)]
+ constraints: dict[_ConstraintKey, list[str]] = {}
# and we are back! Let's fill the matrix
cursor = self.__get_cursor_begin_events()
@@ -166,12 +270,24 @@ class Automata:
line = self.__dot_lines[cursor].split()
origin_state = line[0].replace('"','').replace(',','_')
dest_state = line[2].replace('"','').replace(',','_')
- possible_events = "".join(line[line.index("label")+2:-1]).replace('"', '')
+ possible_events = "".join(line[line.index("label") + 2:-1]).replace('"', '')
for event in possible_events.split("\\n"):
+ event, *constr = event.split(";")
+ if constr:
+ key = _EventConstraintKey(states_dict[origin_state], events_dict[event])
+ constraints[key] = constr
+ # those events reset also on self loops
+ if origin_state == dest_state and "reset" in "".join(constr):
+ self.self_loop_reset_events.add(event)
matrix[states_dict[origin_state]][events_dict[event]] = dest_state
+ else:
+ state = self.__dot_lines[cursor].split("label")[1].split('"')[1]
+ state, *constr = state.replace(" ", "").split("\\n")
+ if constr:
+ constraints[_StateConstraintKey(states_dict[state])] = constr
cursor += 1
- return matrix
+ return matrix, constraints
def __store_init_events(self) -> tuple[list[bool], list[bool]]:
events_start = [False] * len(self.events)
@@ -203,3 +319,13 @@ class Automata:
if any(self.events_start):
return False
return self.events_start_run[self.events.index(event)]
+
+ def is_hybrid_automata(self) -> bool:
+ return bool(self.envs)
+
+ def is_event_constraint(self, key: _ConstraintKey) -> bool:
+ """
+ Given the key in self.constraints return true if it is an event
+ constraint, false if it is a state constraint
+ """
+ return isinstance(key, _EventConstraintKey)
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 06a26bf15a7e..f779d9528af3 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -19,6 +19,7 @@ class Dot2c(Automata):
enum_suffix = ""
enum_states_def = "states"
enum_events_def = "events"
+ enum_envs_def = "envs"
struct_automaton_def = "automaton"
var_automaton_def = "aut"
@@ -61,6 +62,37 @@ class Dot2c(Automata):
return buff
+ def __get_non_stored_envs(self) -> list[str]:
+ return [e for e in self.envs if e not in self.env_stored]
+
+ def __get_enum_envs_content(self) -> list[str]:
+ buff = []
+ # We first place env variables that have a u64 storage.
+ # Those are limited by MAX_HA_ENV_LEN, other variables
+ # are read only and don't require a storage.
+ unstored = self.__get_non_stored_envs()
+ for env in list(self.env_stored) + unstored:
+ buff.append(f"\t{env}{self.enum_suffix},")
+
+ buff.append(f"\tenv_max{self.enum_suffix},")
+ max_stored = unstored[0] if len(unstored) else "env_max"
+ buff.append(f"\tenv_max_stored{self.enum_suffix} = {max_stored}{self.enum_suffix},")
+
+ return buff
+
+ def format_envs_enum(self) -> list[str]:
+ buff = []
+ if self.is_hybrid_automata():
+ buff.append(f"enum {self.enum_envs_def} {{")
+ buff += self.__get_enum_envs_content()
+ buff.append("};\n")
+ buff.append(f"_Static_assert(env_max_stored{self.enum_suffix} <= MAX_HA_ENV_LEN,"
+ ' "Not enough slots");')
+ if {"ns", "us", "ms", "s"}.intersection(self.env_types.values()):
+ buff.append("#define HA_CLK_NS")
+ buff.append("")
+ return buff
+
def get_minimun_type(self) -> str:
min_type = "unsigned char"
@@ -81,6 +113,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(f"\tchar *env_names[env_max{self.enum_suffix}];")
buff.append("\t%s function[state_max%s][event_max%s];" % (min_type, self.enum_suffix, self.enum_suffix))
buff.append("\t%s initial_state;" % min_type)
buff.append("\tbool final_states[state_max%s];" % (self.enum_suffix))
@@ -113,6 +147,17 @@ class Dot2c(Automata):
return buff
+ def format_aut_init_envs_string(self) -> list[str]:
+ buff = []
+ if self.is_hybrid_automata():
+ buff.append("\t.env_names = {")
+ # maintain consistent order with the enum
+ ordered_envs = list(self.env_stored) + self.__get_non_stored_envs()
+ buff.append(self.__get_string_vector_per_line_content(ordered_envs))
+ buff.append("\t},")
+
+ return buff
+
def __get_max_strlen_of_states(self) -> int:
max_state_name = max(self.states, key = len).__len__()
return max(max_state_name, self.invalid_state_str.__len__())
@@ -205,10 +250,12 @@ class Dot2c(Automata):
buff += self.format_states_enum()
buff += self.format_invalid_state()
buff += self.format_events_enum()
+ buff += self.format_envs_enum()
buff += self.format_automaton_definition()
buff += self.format_aut_init_header()
buff += self.format_aut_init_states_string()
buff += self.format_aut_init_events_string()
+ buff += self.format_aut_init_envs_string()
buff += self.format_aut_init_function()
buff += self.format_aut_init_initial_state()
buff += self.format_aut_init_final_states()
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index 6128fe238430..3cdc8cfb6be5 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -8,8 +8,10 @@
# For further information, see:
# Documentation/trace/rv/da_monitor_synthesis.rst
+from collections import deque
from .dot2c import Dot2c
from .generator import Monitor
+from .automata import _EventConstraintKey, _StateConstraintKey
class dot2k(Monitor, Dot2c):
@@ -20,12 +22,16 @@ class dot2k(Monitor, Dot2c):
Monitor.__init__(self, extra_params)
Dot2c.__init__(self, file_path, extra_params.get("model_name"))
self.enum_suffix = "_%s" % self.name
+ self.monitor_class = extra_params["monitor_class"]
def fill_monitor_type(self) -> str:
- return self.monitor_type.upper()
+ buff = [ self.monitor_type.upper() ]
+ buff += self._fill_timer_type()
+ return "\n".join(buff)
def fill_tracepoint_handlers_skel(self) -> str:
buff = []
+ buff += self._fill_hybrid_definitions()
for event in self.events:
buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
buff.append("{")
@@ -77,6 +83,7 @@ class dot2k(Monitor, Dot2c):
#
self.enum_states_def = "states_%s" % self.name
self.enum_events_def = "events_%s" % self.name
+ self.enum_envs_def = f"envs_{self.name}"
self.struct_automaton_def = "automaton_%s" % self.name
self.var_automaton_def = "automaton_%s" % self.name
@@ -107,8 +114,14 @@ class dot2k(Monitor, Dot2c):
("char *", "state"),
("char *", "event"),
]
+ tp_args_error_env = tp_args_error + [("char *", "env")]
+ tp_args_dict = {
+ "event": tp_args_event,
+ "error": tp_args_error,
+ "error_env": tp_args_error_env
+ }
tp_args_id = ("int ", "id")
- tp_args = tp_args_event if tp_type == "event" else tp_args_error
+ tp_args = tp_args_dict[tp_type]
if self.monitor_type == "per_task":
tp_args.insert(0, tp_args_id)
tp_proto_c = ", ".join([a+b for a,b in tp_args])
@@ -117,6 +130,14 @@ class dot2k(Monitor, Dot2c):
buff.append(" TP_ARGS(%s)" % tp_args_c)
return '\n'.join(buff)
+ def _fill_hybrid_definitions(self) -> list:
+ """Stub, not valid for deterministic automata"""
+ return []
+
+ def _fill_timer_type(self) -> list:
+ """Stub, not valid for deterministic automata"""
+ return []
+
def fill_main_c(self) -> str:
main_c = super().fill_main_c()
@@ -127,5 +148,454 @@ class dot2k(Monitor, Dot2c):
main_c = main_c.replace("%%MIN_TYPE%%", min_type)
main_c = main_c.replace("%%NR_EVENTS%%", str(nr_events))
main_c = main_c.replace("%%MONITOR_TYPE%%", monitor_type)
+ main_c = main_c.replace("%%MONITOR_CLASS%%", self.monitor_class)
return main_c
+
+class da2k(dot2k):
+ """Deterministic automata only"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.is_hybrid_automata():
+ raise ValueError("Detected hybrid automata, use the 'ha' class")
+
+class ha2k(dot2k):
+ """Hybrid automata only"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if not self.is_hybrid_automata():
+ raise ValueError("Detected deterministic automata, use the 'da' class")
+ self.trace_h = self._read_template_file("trace_hybrid.h")
+ self.__parse_constraints()
+
+ def fill_monitor_class_type(self) -> str:
+ if self.monitor_type == "per_task":
+ return "HA_MON_EVENTS_ID"
+ return "HA_MON_EVENTS_IMPLICIT"
+
+ def fill_monitor_class(self) -> str:
+ """
+ Used for tracepoint classes, since they are shared we keep da
+ instead of ha (also for the ha specific tracepoints).
+ The tracepoint class is not visible to the tools.
+ """
+ return super().fill_monitor_class()
+
+ def __adjust_value(self, value: str | int, unit: str | None) -> str:
+ """Adjust the value in ns"""
+ try:
+ value = int(value)
+ except ValueError:
+ # it's a constant, a parameter or a function
+ if value.endswith("()"):
+ return value.replace("()", "(ha_mon)")
+ return value
+ match unit:
+ case "us":
+ value *= 10**3
+ case "ms":
+ value *= 10**6
+ case "s":
+ value *= 10**9
+ return str(value) + "ull"
+
+ def __parse_single_constraint(self, rule: dict, value: str) -> str:
+ return f"ha_get_env(ha_mon, {rule["env"]}{self.enum_suffix}, time_ns) {rule["op"]} {value}"
+
+ def __get_constraint_env(self, constr: str) -> str:
+ """Extract the second argument from an ha_ function"""
+ env = constr.split("(")[1].split()[1].rstrip(")").rstrip(",")
+ assert env.rstrip(f"_{self.name}") in self.envs
+ return env
+
+ def __start_to_invariant_check(self, constr: str) -> str:
+ # by default assume the timer has ns expiration
+ env = self.__get_constraint_env(constr)
+ clock_type = "ns"
+ if self.env_types.get(env.rstrip(f"_{self.name}")) == "j":
+ clock_type = "jiffy"
+
+ return f"return ha_check_invariant_{clock_type}(ha_mon, {env}, time_ns)"
+
+ def __start_to_conv(self, constr: str) -> str:
+ """
+ Undo the storage conversion done by ha_start_timer_
+ """
+ return "ha_inv_to_guard" + constr[constr.find("("):]
+
+ def __parse_timer_constraint(self, rule: dict, value: str) -> str:
+ # by default assume the timer has ns expiration
+ clock_type = "ns"
+ if self.env_types.get(rule["env"]) == "j":
+ clock_type = "jiffy"
+
+ return (f"ha_start_timer_{clock_type}(ha_mon, {rule["env"]}{self.enum_suffix},"
+ f" {value}, time_ns)")
+
+ def __format_guard_rules(self, rules: list[str]) -> list[str]:
+ """
+ Merge guard constraints as a single C return statement.
+ If the rules include a stored env, also check its validity.
+ Break lines in a best effort way that tries to keep readability.
+ """
+ if not rules:
+ return []
+
+ invalid_checks = [f"ha_monitor_env_invalid(ha_mon, {env}{self.enum_suffix}) ||"
+ for env in self.env_stored if any(env in rule for rule in rules)]
+ if invalid_checks and len(rules) > 1:
+ rules[0] = "(" + rules[0]
+ rules[-1] = rules[-1] + ")"
+ rules = invalid_checks + rules
+
+ separator = "\n\t\t " if sum(len(r) for r in rules) > 80 else " "
+ return ["res = " + separator.join(rules)]
+
+ def __validate_constraint(self, key: tuple[int, int] | int, constr: str,
+ rule, reset) -> None:
+ # event constrains are tuples and allow both rules and reset
+ # state constraints are only used for expirations (e.g. clk<N)
+ if self.is_event_constraint(key):
+ if not rule and not reset:
+ raise ValueError("Unrecognised event constraint "
+ f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})")
+ if rule and (rule["env"] in self.env_types and
+ rule["env"] not in self.env_stored):
+ raise ValueError("Clocks in hybrid automata always require a storage"
+ f" ({rule["env"]})")
+ else:
+ if not rule:
+ raise ValueError("Unrecognised state constraint "
+ f"({self.states[key]}: {constr})")
+ if rule["env"] not in self.env_stored:
+ raise ValueError("State constraints always require a storage "
+ f"({rule["env"]})")
+ if rule["op"] not in ["<", "<="]:
+ raise ValueError("State constraints must be clock expirations like"
+ f" clk<N ({rule.string})")
+
+ def __parse_constraints(self) -> None:
+ self.guards: dict[_EventConstraintKey, str] = {}
+ self.invariants: dict[_StateConstraintKey, str] = {}
+ for key, constraint in self.constraints.items():
+ rules = []
+ resets = []
+ for c, sep in self._split_constraint_expr(constraint):
+ rule = self.constraint_rule.search(c)
+ reset = self.constraint_reset.search(c)
+ self.__validate_constraint(key, c, rule, reset)
+ if rule:
+ value = rule["val"]
+ value_len = len(rule["val"])
+ unit = None
+ if rule.groupdict().get("unit"):
+ value_len += len(rule["unit"])
+ unit = rule["unit"]
+ c = c[:-(value_len)]
+ value = self.__adjust_value(value, unit)
+ if self.is_event_constraint(key):
+ c = self.__parse_single_constraint(rule, value)
+ if sep:
+ c += f" {sep}"
+ else:
+ c = self.__parse_timer_constraint(rule, value)
+ rules.append(c)
+ if reset:
+ c = f"ha_reset_env(ha_mon, {reset["env"]}{self.enum_suffix}, time_ns)"
+ resets.append(c)
+ if self.is_event_constraint(key):
+ res = self.__format_guard_rules(rules) + resets
+ self.guards[key] = ";".join(res)
+ else:
+ self.invariants[key] = rules[0]
+
+ def __fill_verify_invariants_func(self) -> list[str]:
+ buff = []
+ if not self.invariants:
+ return []
+
+ buff.append(
+f"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+\t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
+\t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns)
+{{""")
+
+ _else = ""
+ for state, constr in sorted(self.invariants.items()):
+ check_str = self.__start_to_invariant_check(constr)
+ buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})")
+ buff.append(f"\t\t{check_str};")
+ _else = "else "
+
+ buff.append("\treturn true;\n}\n")
+ return buff
+
+ def __fill_convert_inv_guard_func(self) -> list[str]:
+ buff = []
+ if not self.invariants:
+ return []
+
+ conflict_guards, conflict_invs = self.__find_inv_conflicts()
+ if not conflict_guards and not conflict_invs:
+ return []
+
+ buff.append(
+f"""static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+\t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
+\t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns)
+{{""")
+ buff.append("\tif (curr_state == next_state)\n\t\treturn;")
+
+ _else = ""
+ for state, constr in sorted(self.invariants.items()):
+ # a state with invariant can reach us without reset
+ # multiple conflicts must have the same invariant, otherwise we cannot
+ # know how to reset the value
+ conf_i = [start for start, end in conflict_invs if end == state]
+ # we can reach a guard without reset
+ conf_g = [e for s, e in conflict_guards if s == state]
+ if not conf_i and not conf_g:
+ continue
+ buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})")
+
+ buff.append(f"\t\t{self.__start_to_conv(constr)};")
+ _else = "else "
+
+ buff.append("}\n")
+ return buff
+
+ def __fill_verify_guards_func(self) -> list[str]:
+ buff = []
+ if not self.guards:
+ return []
+
+ buff.append(
+f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+\t\t\t\t enum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
+\t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns)
+{{
+\tbool res = true;
+""")
+
+ _else = ""
+ for edge, constr in sorted(self.guards.items()):
+ buff.append(f"\t{_else}if (curr_state == "
+ f"{self.states[edge[0]]}{self.enum_suffix} && "
+ f"event == {self.events[edge[1]]}{self.enum_suffix})")
+ if constr.count(";") > 0:
+ buff[-1] += " {"
+ buff += [f"\t\t{c};" for c in constr.split(";")]
+ if constr.count(";") > 0:
+ _else = "} else "
+ else:
+ _else = "else "
+ if _else[0] == "}":
+ buff.append("\t}")
+ buff.append("\treturn res;\n}\n")
+ return buff
+
+ def __find_inv_conflicts(self) -> tuple[set[tuple[int, _EventConstraintKey]],
+ set[tuple[int, _StateConstraintKey]]]:
+ """
+ Run a breadth first search from all states with an invariant.
+ Find any conflicting constraints reachable from there, this can be
+ another state with an invariant or an edge with a non-reset guard.
+ Stop when we find a reset.
+
+ Return the set of conflicting guards and invariants as tuples of
+ conflicting state and constraint key.
+ """
+ conflict_guards: set[tuple[int, _EventConstraintKey]] = set()
+ conflict_invs: set[tuple[int, _StateConstraintKey]] = set()
+ for start_idx in self.invariants:
+ queue = deque([(start_idx, 0)]) # (state_idx, distance)
+ env = self.__get_constraint_env(self.invariants[start_idx])
+
+ while queue:
+ curr_idx, distance = queue.popleft()
+
+ # Check state condition
+ if curr_idx != start_idx and curr_idx in self.invariants:
+ conflict_invs.add((start_idx, _StateConstraintKey(curr_idx)))
+ continue
+
+ # Check if we should stop
+ if distance > len(self.states):
+ break
+ if curr_idx != start_idx and distance > 1:
+ continue
+
+ for event_idx, next_state_name in enumerate(self.function[curr_idx]):
+ if next_state_name == self.invalid_state_str:
+ continue
+ curr_guard = self.guards.get((curr_idx, event_idx), "")
+ if "reset" in curr_guard and env in curr_guard:
+ continue
+
+ if env in curr_guard:
+ conflict_guards.add((start_idx,
+ _EventConstraintKey(curr_idx, event_idx)))
+ continue
+
+ next_idx = self.states.index(next_state_name)
+ queue.append((next_idx, distance + 1))
+
+ return conflict_guards, conflict_invs
+
+ def __fill_setup_invariants_func(self) -> list[str]:
+ buff = []
+ if not self.invariants:
+ return []
+
+ buff.append(
+f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+\t\t\t\t enum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
+\t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns)
+{{""")
+
+ conditions = ["next_state == curr_state"]
+ conditions += [f"event != {e}{self.enum_suffix}"
+ for e in self.self_loop_reset_events]
+ condition_str = " && ".join(conditions)
+ buff.append(f"\tif ({condition_str})\n\t\treturn;")
+
+ _else = ""
+ for state, constr in sorted(self.invariants.items()):
+ buff.append(f"\t{_else}if (next_state == {self.states[state]}{self.enum_suffix})")
+ buff.append(f"\t\t{constr};")
+ _else = "else "
+
+ for state in self.invariants:
+ buff.append(f"\telse if (curr_state == {self.states[state]}{self.enum_suffix})")
+ buff.append("\t\tha_cancel_timer(ha_mon);")
+
+ buff.append("}\n")
+ return buff
+
+ def __fill_constr_func(self) -> list[str]:
+ buff = []
+ if not self.constraints:
+ return []
+
+ buff.append(
+"""/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * Due to the different representations between invariants and guards, there is
+ * a function to convert it in case invariants or guards are reachable from
+ * another invariant without reset. Those are not present if not required in
+ * the model. This is all automatic but is worth checking because it may show
+ * errors in the model (e.g. missing resets).
+ */""")
+
+ buff += self.__fill_verify_invariants_func()
+ inv_conflicts = self.__fill_convert_inv_guard_func()
+ buff += inv_conflicts
+ buff += self.__fill_verify_guards_func()
+ buff += self.__fill_setup_invariants_func()
+
+ buff.append(
+f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+\t\t\t\t enum {self.enum_states_def} curr_state, enum {self.enum_events_def} event,
+\t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns)
+{{""")
+
+ if self.invariants:
+ buff.append("\tif (!ha_verify_invariants(ha_mon, curr_state, "
+ "event, next_state, time_ns))\n\t\treturn false;\n")
+ if inv_conflicts:
+ buff.append("\tha_convert_inv_guard(ha_mon, curr_state, event, "
+ "next_state, time_ns);\n")
+
+ if self.guards:
+ buff.append("\tif (!ha_verify_guards(ha_mon, curr_state, event, "
+ "next_state, time_ns))\n\t\treturn false;\n")
+
+ if self.invariants:
+ buff.append("\tha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);\n")
+
+ buff.append("\treturn true;\n}\n")
+ return buff
+
+ def __fill_env_getter(self, env: str) -> str:
+ if env in self.env_types:
+ match self.env_types[env]:
+ case "ns" | "us" | "ms" | "s":
+ return "ha_get_clk_ns(ha_mon, env, time_ns);"
+ case "j":
+ return "ha_get_clk_jiffy(ha_mon, env);"
+ return f"/* XXX: how do I read {env}? */"
+
+ def __fill_env_resetter(self, env: str) -> str:
+ if env in self.env_types:
+ match self.env_types[env]:
+ case "ns" | "us" | "ms" | "s":
+ return "ha_reset_clk_ns(ha_mon, env, time_ns);"
+ case "j":
+ return "ha_reset_clk_jiffy(ha_mon, env);"
+ return f"/* XXX: how do I reset {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(f"#define {func_name}(ha_mon) "
+ f"/* XXX: what is {func_name}(ha_mon)? */\n")
+ else:
+ buff.append(f"static inline u64 {func_name}(struct ha_monitor *ha_mon)\n{{")
+ buff.append(f"\treturn /* XXX: what is {func_name}(ha_mon)? */;")
+ buff.append("}\n")
+ elif var.isupper():
+ buff.append(f"#define {var} /* XXX: what is {var}? */\n")
+ else:
+ buff.append(f"static u64 {var} = /* XXX: default value */;")
+ buff.append(f"module_param({var}, ullong, 0644);\n")
+ 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, "
+ f"enum envs{self.enum_suffix} env, u64 time_ns)\n{{")
+ _else = ""
+ for env in self.envs:
+ buff.append(f"\t{_else}if (env == {env}{self.enum_suffix})")
+ buff.append(f"\t\treturn {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, "
+ f"enum envs{self.enum_suffix} env, u64 time_ns)\n{{")
+ _else = ""
+ for env in self.env_stored:
+ buff.append(f"\t{_else}if (env == {env}{self.enum_suffix})")
+ buff.append(f"\t\t{self.__fill_env_resetter(env)}")
+ _else = "else "
+ buff.append("}\n")
+ return buff
+
+ def _fill_hybrid_definitions(self) -> list[str]:
+ return self.__fill_hybrid_get_reset_functions() + self.__fill_constr_func()
+
+ def _fill_timer_type(self) -> list:
+ if self.invariants:
+ return [
+ "/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */",
+ "#define HA_TIMER_TYPE HA_TIMER_HRTIMER"
+ ]
+ return []
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 3441385c1177..b80af3fd6701 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -255,12 +255,14 @@ class Monitor(RVGenerator):
monitor_class_type = self.fill_monitor_class_type()
tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
+ tracepoint_args_skel_error_env = self.fill_tracepoint_args_skel("error_env")
trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
+ trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%", tracepoint_args_skel_error_env)
return trace_h
def print_files(self):
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index a14e4f0883db..bf0999f6657a 100644
--- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -21,7 +21,7 @@
*/
#define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
#include "%%MODEL_NAME%%.h"
-#include <rv/da_monitor.h>
+#include <rv/%%MONITOR_CLASS%%_monitor.h>
/*
* This is the instrumentation part of the monitor.
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
new file mode 100644
index 000000000000..c8290e9ba2f4
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_%%MODEL_NAME_UP%%
+DEFINE_EVENT(event_%%MONITOR_CLASS%%, event_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_EVENT%%);
+
+DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR%%);
+
+DEFINE_EVENT(error_env_%%MONITOR_CLASS%%, error_env_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%);
+#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */
--
2.53.0
^ permalink raw reply related
* [PATCH v8 05/12] Documentation/rv: Add documentation about hybrid automata
From: Gabriele Monaco @ 2026-03-30 11:10 UTC (permalink / raw)
To: linux-kernel, Steven Rostedt, Nam Cao, Juri Lelli,
Gabriele Monaco, Jonathan Corbet, linux-trace-kernel, linux-doc
Cc: Juri Lelli, Tomas Glozar, Clark Williams, John Kacur
In-Reply-To: <20260330111010.153663-1-gmonaco@redhat.com>
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
Reviewed-by: Nam Cao <namcao@linutronix.de>
Reviewed-by: Juri Lelli <juri.lelli@redhat.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V8:
* Minor improvement in docs
.../trace/rv/deterministic_automata.rst | 2 +-
Documentation/trace/rv/hybrid_automata.rst | 341 ++++++++++++++++++
Documentation/trace/rv/index.rst | 1 +
Documentation/trace/rv/monitor_synthesis.rst | 117 +++++-
4 files changed, 458 insertions(+), 3 deletions(-)
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..f20d489f18c1
--- /dev/null
+++ b/Documentation/trace/rv/hybrid_automata.rst
@@ -0,0 +1,341 @@
+Hybrid Automata
+===============
+
+Hybrid automata are an extension of deterministic automata, there are several
+definitions of hybrid automata in the literature. The adaptation implemented
+here is formally denoted by G and defined as a 7-tuple:
+
+ *G* = { *X*, *E*, *V*, *f*, x\ :subscript:`0`, X\ :subscript:`m`, *i* }
+
+- *X* is the set of states;
+- *E* is the finite set of events;
+- *V* is the finite set of environment variables;
+- x\ :subscript:`0` is the initial state;
+- X\ :subscript:`m` (subset of *X*) is the set of marked (or final) states.
+- *f* : *X* x *E* x *C(V)* -> *X* is the transition function.
+ It defines the state transition in the occurrence of an event from *E* in the
+ state *X*. Unlike deterministic automata, the transition function also
+ includes guards from the set of all possible constraints (defined as *C(V)*).
+ Guards can be true or false with the valuation of *V* when the event occurs,
+ and the transition is possible only when constraints are true. Similarly to
+ deterministic automata, the occurrence of the event in *E* in a state in *X*
+ has a deterministic next state from *X*, if the guard is true.
+- *i* : *X* -> *C'(V)* is the invariant assignment function, this is a
+ constraint assigned to each state in *X*, every state in *X* must be left
+ before the invariant turns to false. We can omit the representation of
+ invariants whose value is true regardless of the valuation of *V*.
+
+The set of all possible constraints *C(V)* is defined according to the
+following grammar:
+
+ g = v < c | v > c | v <= c | v >= c | v == c | v != c | g && g | true
+
+With v a variable in *V* and c a numerical value.
+
+We define the special case of hybrid automata whose variables grow with uniform
+rates as timed automata. In this case, the variables are called clocks.
+As the name implies, timed automata can be used to describe real time.
+Additionally, clocks support another type of guard which always evaluates to true:
+
+ reset(v)
+
+The reset constraint is used to set the value of a clock to 0.
+
+The set of invariant constraints *C'(V)* is a subset of *C(V)* including only
+constraint of the form:
+
+ g = v < c | true
+
+This simplifies the implementation as a clock expiration is a necessary and
+sufficient condition for the violation of invariants while still allowing more
+complex constraints to be specified as guards.
+
+It is important to note that any hybrid automaton is a valid deterministic
+automaton with additional guards and invariants. Those can only further
+constrain what transitions are valid but it is not possible to define
+transition functions starting from the same state in *X* and the same event in
+*E* but ending up in different states in *X* based on the valuation of *V*.
+
+Examples
+--------
+
+Wip as hybrid automaton
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The 'wip' (wakeup in preemptive) example introduced as a deterministic automaton
+can also be described as:
+
+- *X* = { ``any_thread_running`` }
+- *E* = { ``sched_waking`` }
+- *V* = { ``preemptive`` }
+- x\ :subscript:`0` = ``any_thread_running``
+- X\ :subscript:`m` = {``any_thread_running``}
+- *f* =
+ - *f*\ (``any_thread_running``, ``sched_waking``, ``preemptive==0``) = ``any_thread_running``
+- *i* =
+ - *i*\ (``any_thread_running``) = ``true``
+
+Which can be represented graphically as::
+
+ |
+ |
+ v
+ #====================# sched_waking;preemptive==0
+ H H ------------------------------+
+ H any_thread_running H |
+ H H <-----------------------------+
+ #====================#
+
+In this example, by using the preemptive state of the system as an environment
+variable, we can assert this constraint on ``sched_waking`` without requiring
+preemption events (as we would in a deterministic automaton), which can be
+useful in case those events are not available or not reliable on the system.
+
+Since all the invariants in *i* are true, we can omit them from the representation.
+
+Stall model with guards (iteration 1)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As a sample timed automaton we can define 'stall' as:
+
+- *X* = { ``dequeued``, ``enqueued``, ``running``}
+- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
+- *V* = { ``clk`` }
+- x\ :subscript:`0` = ``dequeue``
+- X\ :subscript:`m` = {``dequeue``}
+- *f* =
+ - *f*\ (``enqueued``, ``switch_in``, ``clk < threshold``) = ``running``
+ - *f*\ (``running``, ``dequeue``) = ``dequeued``
+ - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
+- *i* = *omitted as all true*
+
+Graphically represented as::
+
+ |
+ |
+ v
+ #============================#
+ H dequeued H <+
+ #============================# |
+ | |
+ | enqueue; reset(clk) |
+ v |
+ +----------------------------+ |
+ | enqueued | | dequeue
+ +----------------------------+ |
+ | |
+ | switch_in; clk < threshold |
+ v |
+ +----------------------------+ |
+ | running | -+
+ +----------------------------+
+
+This model imposes that the time between when a task is enqueued (it becomes
+runnable) and when the task gets to run must be lower than a certain threshold.
+A failure in this model means that the task is starving.
+One problem in using guards on the edges in this case is that the model will
+not report a failure until the ``switch_in`` event occurs. This means that,
+according to the model, it is valid for the task never to run.
+
+Stall model with invariants (iteration 2)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first iteration isn't exactly what was intended, we can change the model as:
+
+- *X* = { ``dequeued``, ``enqueued``, ``running``}
+- *E* = { ``enqueue``, ``dequeue``, ``switch_in``}
+- *V* = { ``clk`` }
+- x\ :subscript:`0` = ``dequeue``
+- X\ :subscript:`m` = {``dequeue``}
+- *f* =
+ - *f*\ (``enqueued``, ``switch_in``) = ``running``
+ - *f*\ (``running``, ``dequeue``) = ``dequeued``
+ - *f*\ (``dequeued``, ``enqueue``, ``reset(clk)``) = ``enqueued``
+- *i* =
+ - *i*\ (``enqueued``) = ``clk < threshold``
+
+Graphically::
+
+ |
+ |
+ v
+ #=========================#
+ H dequeued H <+
+ #=========================# |
+ | |
+ | enqueue; reset(clk) |
+ v |
+ +-------------------------+ |
+ | enqueued | |
+ | clk < threshold | | dequeue
+ +-------------------------+ |
+ | |
+ | switch_in |
+ v |
+ +-------------------------+ |
+ | running | -+
+ +-------------------------+
+
+In this case, we moved the guard as an invariant to the ``enqueued`` state,
+this means we not only forbid the occurrence of ``switch_in`` when ``clk`` is
+past the threshold but also mark as invalid in case we are *still* in
+``enqueued`` after the threshold. This model is effectively in an invalid state
+as soon as a task is starving, rather than when the starving task finally runs.
+
+Hybrid Automaton in C
+---------------------
+
+The definition of hybrid automata in C is heavily based on the deterministic
+automata one. Specifically, we add the set of environment variables and the
+constraints (both guards on transitions and invariants on states) as follows.
+This is a combination of both iterations of the stall example::
+
+ /* enum representation of X (set of states) to be used as index */
+ enum states {
+ dequeued,
+ enqueued,
+ running,
+ state_max,
+ };
+
+ #define INVALID_STATE state_max
+
+ /* enum representation of E (set of events) to be used as index */
+ enum events {
+ dequeue,
+ enqueue,
+ switch_in,
+ event_max,
+ };
+
+ /* enum representation of V (set of environment variables) to be used as index */
+ enum envs {
+ clk,
+ 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 == switch_in)
+ res = get_env(clk) < threshold;
+ else if (curr_state == dequeued && event == enqueue)
+ reset_env(clk);
+
+ /* Validate invariants in i */
+ if (next_state == curr_state || !res)
+ return res;
+ if (next_state == enqueued)
+ ha_start_timer_jiffy(ha_mon, clk, threshold_jiffies);
+ else if (curr_state == enqueued)
+ res = !ha_cancel_timer(ha_mon);
+ return res;
+ }
+
+The function ``verify_constraint``, here reported as simplified, checks guards,
+performs resets and starts timers to validate invariants according to
+specification, those cannot easily be represented in the automaton struct.
+Due to the complex nature of environment variables, the user needs to provide
+functions to get and reset environment variables that are not common clocks
+(e.g. clocks with ns or jiffy granularity).
+Since invariants are only defined as clock expirations (e.g. *clk <
+threshold*), reaching the expiration of a timer armed when entering the state
+is in fact a failure in the model and triggers a reaction. Leaving the state
+stops the timer.
+
+It is important to note that timers implemented with hrtimers introduce
+overhead, if the monitor has several instances (e.g. all tasks) this can become
+an issue. The impact can be decreased using the timer wheel (``HA_TIMER_TYPE``
+set to ``HA_TIMER_WHEEL``), this lowers the responsiveness of the timer without
+damaging the accuracy of the model, since the invariant condition is checked
+before disabling the timer in case the callback is late.
+Alternatively, if the monitor is guaranteed to *eventually* leave the state and
+the incurred delay to wait for the next event is acceptable, guards can be used
+in place of invariants, as seen in the stall example.
+
+Graphviz .dot format
+--------------------
+
+Also the Graphviz representation of hybrid automata is an extension of the
+deterministic automata one. Specifically, guards can be provided in the event
+name separated by ``;``::
+
+ "state_start" -> "state_dest" [ label = "sched_waking;preemptible==0;reset(clk)" ];
+
+Invariant can be specified in the state label (not the node name!) separated by ``\n``::
+
+ "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+
+Constraints can be specified as valid C comparisons and allow spaces, the first
+element of the comparison must be the clock while the second is a numerical or
+parametrised value. Guards allow comparisons to be combined with boolean
+operations (``&&`` and ``||``), resets must be separated from other constraints.
+
+This is the full example of the last version of the 'stall' model in DOT::
+
+ digraph state_automaton {
+ {node [shape = circle] "enqueued"};
+ {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+ {node [shape = doublecircle] "dequeued"};
+ {node [shape = circle] "running"};
+ "__init_dequeued" -> "dequeued";
+ "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+ "running" [label = "running"];
+ "dequeued" [label = "dequeued"];
+ "enqueued" -> "running" [ label = "switch_in" ];
+ "running" -> "dequeued" [ label = "dequeue" ];
+ "dequeued" -> "enqueued" [ label = "enqueue;reset(clk)" ];
+ { rank = min ;
+ "__init_dequeued";
+ "dequeued";
+ }
+ }
+
+References
+----------
+
+One book covering model checking and timed automata is::
+
+ Christel Baier and Joost-Pieter Katoen: Principles of Model Checking,
+ The MIT Press, 2008.
+
+Hybrid automata are described in detail in::
+
+ Thomas Henzinger: The theory of hybrid automata,
+ Proceedings 11th Annual IEEE Symposium on Logic in Computer Science, 1996.
diff --git a/Documentation/trace/rv/index.rst b/Documentation/trace/rv/index.rst
index a2812ac5cfeb..ad298784bda2 100644
--- a/Documentation/trace/rv/index.rst
+++ b/Documentation/trace/rv/index.rst
@@ -9,6 +9,7 @@ Runtime Verification
runtime-verification.rst
deterministic_automata.rst
linear_temporal_logic.rst
+ hybrid_automata.rst
monitor_synthesis.rst
da_monitor_instrumentation.rst
monitor_wip.rst
diff --git a/Documentation/trace/rv/monitor_synthesis.rst b/Documentation/trace/rv/monitor_synthesis.rst
index cc5f97977a29..2c1b5a0ae154 100644
--- a/Documentation/trace/rv/monitor_synthesis.rst
+++ b/Documentation/trace/rv/monitor_synthesis.rst
@@ -18,8 +18,8 @@ functions that glue the monitor to the system reference model, and the
trace output as a reaction to event parsing and exceptions, as depicted
below::
- Linux +----- RV Monitor ----------------------------------+ Formal
- Realm | | Realm
+ Linux +---- RV Monitor ----------------------------------+ Formal
+ Realm | | Realm
+-------------------+ +----------------+ +-----------------+
| Linux kernel | | Monitor | | Reference |
| Tracing | -> | Instance(s) | <- | Model |
@@ -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,118 @@ the task, the monitor may need some time to start validating tasks which have
been running before the monitor is enabled. Therefore, it is recommended to
start the tasks of interest after enabling the monitor.
+rv/ha_monitor.h
++++++++++++++++
+
+The implementation of hybrid automaton monitors derives directly from the
+deterministic automaton one. Despite using a different header
+(``ha_monitor.h``) the functions to handle events are the same (e.g.
+``da_handle_event``).
+
+Additionally, the `rvgen` tool populates skeletons for the
+``ha_verify_constraint``, ``ha_get_env`` and ``ha_reset_env`` based on the
+monitor specification in the monitor source file.
+
+``ha_verify_constraint`` is typically ready as it is generated by `rvgen`:
+
+* standard constraints on edges are turned into the form::
+
+ res = ha_get_env(ha_mon, ENV) < VALUE;
+
+* reset constraints are turned into the form::
+
+ ha_reset_env(ha_mon, ENV);
+
+* constraints on the state are implemented using timers
+
+ - armed before entering the state
+
+ - cancelled while entering any other state
+
+ - untouched if the state does not change as a result of the event
+
+ - checked if the timer expired but the callback did not run
+
+ - available implementation are `HA_TIMER_HRTIMER` and `HA_TIMER_WHEEL`
+
+ - hrtimers are more precise but may have higher overhead
+
+ - select by defining `HA_TIMER_TYPE` before including the header::
+
+ #define HA_TIMER_TYPE HA_TIMER_HRTIMER
+
+Constraint values can be specified in different forms:
+
+* literal value (with optional unit). E.g.::
+
+ preemptive == 0
+ clk < 100ns
+ threshold <= 10j
+
+* constant value (uppercase string). E.g.::
+
+ clk < MAX_NS
+
+* parameter (lowercase string). E.g.::
+
+ clk <= threshold_jiffies
+
+* macro (uppercase string with parentheses). E.g.::
+
+ clk < MAX_NS()
+
+* function (lowercase string with parentheses). E.g.::
+
+ clk <= threshold_jiffies()
+
+In all cases, `rvgen` will try to understand the type of the environment
+variable from the name or unit. For instance, constants or parameters
+terminating with ``_NS`` or ``_jiffies`` are intended as clocks with ns and jiffy
+granularity, respectively. Literals with measure unit `j` are jiffies and if a
+time unit is specified (`ns` to `s`), `rvgen` will convert the value to `ns`.
+
+Constants need to be defined by the user (but unlike the name, they don't
+necessarily need to be defined as constants). Parameters get converted to
+module parameters and the user needs to provide a default value.
+Also function and macros are defined by the user, by default they get as an
+argument the ``ha_monitor``, a common usage would be to get the required value
+from the target, e.g. the task in per-task monitors, using the helper
+``ha_get_target(ha_mon)``.
+
+If `rvgen` determines that the variable is a clock, it provides the getter and
+resetter based on the unit. Otherwise, the user needs to provide an appropriate
+definition.
+Typically non-clock environment variables are not reset. In such case only the
+getter skeleton will be present in the file generated by `rvgen`.
+For instance, the getter for preemptive can be filled as::
+
+ static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env)
+ {
+ if (env == preemptible)
+ return preempt_count() == 0;
+ return ENV_INVALID_VALUE;
+ }
+
+The function is supplied the ``ha_mon`` parameter in case some storage is
+required (as it is for clocks), but environment variables without reset do not
+require a storage and can ignore that argument.
+The number of environment variables requiring a storage is limited by
+``MAX_HA_ENV_LEN``, however such limitation doesn't stand for other variables.
+
+Finally, constraints on states are only valid for clocks and only if the
+constraint is of the form `clk < N`. This is because such constraints are
+implemented with the expiration of a timer.
+Typically the clock variables are reset just before arming the timer, but this
+doesn't have to be the case and the available functions take care of it.
+It is a responsibility of per-task monitors to make sure no timer is left
+running when the task exits.
+
+By default the generator implements timers with hrtimers (setting
+``HA_TIMER_TYPE`` to ``HA_TIMER_HRTIMER``), this gives better responsiveness
+but higher overhead. The timer wheel (``HA_TIMER_WHEEL``) is a good alternative
+for monitors with several instances (e.g. per-task) that achieves lower
+overhead with increased latency, yet without compromising precision.
+
Final remarks
-------------
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox