* Re: [PATCH] Documentation/rv: Replace stale website link
From: Gabriele Monaco @ 2026-04-27 9:57 UTC (permalink / raw)
To: Jonathan Corbet, rdunlap, Steven Rostedt, linux-trace-kernel,
linux-doc, linux-kernel
Cc: matteo.martelli, skhan
In-Reply-To: <875x5crb4g.fsf@trenco.lwn.net>
On Mon, 2026-04-27 at 03:44 -0600, Jonathan Corbet wrote:
> Since, as you say, it can be found online, is there a reason not to
> include a link here?
Mmh, perhaps being overly cautious for the link not to break again?
The paper is published so I assume it's always going to be available in
some way. It is currently hosted by the university at [1], which may be
unlikely to change, and can be found via DOI at [2], which should never
change (at least that's what I believe a DOI is for) but brings to the
publisher's website rather than the open-access PDF.
I think the reference to the paper I included is robust yet easy to use
with any scientific or even general purpose search engine. But if you
believe using either of the two links is more appropriate, I can send a
V2 with the change.
Thanks,
Gabriele
[1] -
https://www.iris.sssup.it/bitstream/11382/533630/1/Elsevier-JSA-2020.pdf
[2] - https://doi.org/10.1016/j.sysarc.2020.101729
^ permalink raw reply
* Re: [PATCH] Documentation/rv: Replace stale website link
From: Jonathan Corbet @ 2026-04-27 10:09 UTC (permalink / raw)
To: Gabriele Monaco, rdunlap, Steven Rostedt, linux-trace-kernel,
linux-doc, linux-kernel
Cc: matteo.martelli, skhan
In-Reply-To: <6d7e529c7cb0ad599669e3f33e5b6168e92a8861.camel@redhat.com>
Gabriele Monaco <gmonaco@redhat.com> writes:
> On Mon, 2026-04-27 at 03:44 -0600, Jonathan Corbet wrote:
>> Since, as you say, it can be found online, is there a reason not to
>> include a link here?
>
> Mmh, perhaps being overly cautious for the link not to break again?
>
> The paper is published so I assume it's always going to be available in
> some way. It is currently hosted by the university at [1], which may be
> unlikely to change, and can be found via DOI at [2], which should never
> change (at least that's what I believe a DOI is for) but brings to the
> publisher's website rather than the open-access PDF.
>
> I think the reference to the paper I included is robust yet easy to use
> with any scientific or even general purpose search engine. But if you
> believe using either of the two links is more appropriate, I can send a
> V2 with the change.
I will defer to others in the end, but to me it seems that we should
make life easier for our readers whenever we can. Providing a link
seems better than requiring them to search for it themselves.
Thanks,
jon
^ permalink raw reply
* Re: [PATCH v3 2/4] mm: kick writeback flusher for IOCB_DONTCACHE with targeted dirty tracking
From: Jeff Layton @ 2026-04-27 10:44 UTC (permalink / raw)
To: Ritesh Harjani, Alexander Viro, Christian Brauner, Jan Kara,
Matthew Wilcox (Oracle), Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Mike Snitzer, Jens Axboe,
Christoph Hellwig, Kairui Song, Qi Zheng, Shakeel Butt,
Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chuck Lever
Cc: linux-fsdevel, linux-kernel, linux-nfs, linux-mm,
linux-trace-kernel
In-Reply-To: <qzo1s6a4.ritesh.list@gmail.com>
On Mon, 2026-04-27 at 04:01 +0530, Ritesh Harjani wrote:
> Jeff Layton <jlayton@kernel.org> writes:
>
> > The IOCB_DONTCACHE writeback path in generic_write_sync() calls
> > filemap_flush_range() on every write, submitting writeback inline in
> > the writer's context. Perf lock contention profiling shows the
> > performance problem is not lock contention but the writeback submission
> > work itself — walking the page tree and submitting I/O blocks the writer
> > for milliseconds, inflating p99.9 latency from 23ms (buffered) to 93ms
> > (dontcache).
> >
> > Replace the inline filemap_flush_range() call with a flusher kick that
> > drains dirty pages in the background. This moves writeback submission
> > completely off the writer's hot path.
> >
> > To avoid flushing unrelated buffered dirty data, add a dedicated
> > WB_start_dontcache bit and wb_check_start_dontcache() handler that uses
> > the new NR_DONTCACHE_DIRTY counter to determine how many pages to write
> > back. The flusher writes back that many pages from the oldest dirty
> > inodes (not restricted to dontcache-specific inodes). This helps
> > preserve I/O batching while limiting the scope of expedited writeback.
> >
>
> Yup, so, we wakeup the writeback flusher, which will write those many
> "number" of dirty pages. Those dirty pages written by writeback, can be
> of any type though, can be DONTCACHE or normal (non-dontcache) dirty
> pages. IIUC, writeback doesn't distinguish between them while writing.
>
Correct. This was the approach that Jan and HCH suggested in the
responses to the last posting.
>
> IMO, what we could also include in the commit msg is why is this above
> approach taken? IIUC, that is because, by writing NR_DONTCACHE_DIRTY
> pages, it still reduces the page cache pressure and still reduces the
> amount of work that the reclaim has to do, even though some of those
> pages maybe non-dontcache pages, in case if there was a parallel
> buffered write in the system.
>
Good suggestion. I'll add that.
>
> Also should the following change be documented somewhere? Like in Man
> page maybe? i.e.
> Earlier RWF_DONTCACHE writes made sure that those dirty pages are
> immediately submitted for writeback and completion would release those
> pages. But now, in certain cases when there is a mixed buffered write in
> the system, those dontcache dirty pages might be written back after a
> delay (whenever the next time writeback kicks in).
> However for RWF_DONTCACHE reads, it should not affect anything.
>
Looks like DONTCACHE is documented in the preadv/writev manpage. Here's
the current blurb about writes:
Additionally, any range dirtied by a write operation with RWF_DONT‐
CACHE set will get kicked off for writeback. This is similar to
calling sync_file_range(2) with SYNC_FILE_RANGE_WRITE to start
writeback on the given range. RWF_DONTCACHE is a hint, or best ef‐
fort, where no hard guarantees are given on the state of the page
cache once the operation completes.
I don't think this verbiage is invalid after this change. Kicking off
writeback is still just a hint, like it was before. We could mention
about how that I/O can compete with regular buffered I/O, but it seems
a bit like we're adding info that will just be confusing for users.
> > Like WB_start_all, the WB_start_dontcache bit coalesces multiple
> > DONTCACHE writes into a single flusher wakeup without per-write
> > allocations.
> >
> > Also add WB_REASON_DONTCACHE as a new writeback reason for tracing
> > visibility, and target the correct cgroup writeback domain via
> > unlocked_inode_to_wb_begin().
> >
> > dontcache-bench results on dual-socket Xeon Gold 6138 (80 CPUs, 256 GB
> > RAM, Samsung MZ1LB1T9HALS 1.7 TB NVMe, local XFS, io_uring, file size
> > ~503 GB, compared to a v6.19-ish baseline):
> >
>
> Can we please also test parallel buffered writes and dontcache writes?
> Since this patch series definitely affects that.
>
> BTW - adding these numbers in the commit msg itself is much helpful.
>
To be clear, this only affects DONTCACHE, not normal buffered writes,
but I guess you're referring to the fact that DONTCACHE and buffered
writes can compete now.
Can you clarify specifically what you'd like me to test here? Are you
saying you want me to test parallel and buffered writes together at the
same time (i.e. make them compete?).
I should be able to do that for the local benchmarks, but nfsd's iomode
settings are global and that won't be possible there.
> > Single-client sequential write (MB/s):
> > baseline patched change
> > buffered 1449.8 1440.1 -0.7%
> > dontcache 1347.9 1461.5 +8.4%
> > direct 1450.0 1440.1 -0.7%
> >
> > Single-client sequential write latency (us):
> > baseline patched change
> > dontcache p50 3031.0 10551.3 +248.1%
> > dontcache p99 74973.2 21626.9 -71.2%
> > dontcache p99.9 85459.0 23199.7 -72.9%
> >
> > Single-client random write (MB/s):
> > baseline patched change
> > dontcache 284.2 295.4 +3.9%
> >
> > Single-client random write p99.9 latency (us):
> > baseline patched change
> > dontcache 2277.4 872.4 -61.7%
> >
> > Multi-writer aggregate throughput (MB/s):
>
> Can you please help describe this test scenario if possible.. In above
> you mentioned we are writing file_size as 2x RAM_SIZE. But your
> multi-client tests says something else..
>
> local num_clients=4
> + mem_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
> + client_size="$(( mem_kb / 1024 / num_clients ))M"
>
> Also the multi-writer case is spawning parallel fio jobs, and then
> parsing and aggregating the bandwidth results instead of using fio to
> spawn multiple parallel threads... which is ok, but a bit wierd.
> Why not let fio do the aggregate bandwidth, and latency calculation
> instead?
>
That's what I get for asking Claude to roll a testsuite. I'm not that
well-versed in fio, but that makes sense. I'll have a look at reworking
it along those lines.
> > baseline patched change
> > buffered 1619.5 1611.2 -0.5%
> > dontcache 1281.1 1629.4 +27.2%
> > direct 1545.4 1609.4 +4.1%
> >
> > Mixed-mode noisy neighbor (dontcache writer + buffered readers):
> > baseline patched change
> > writer (MB/s) 1297.6 1471.1 +13.4%
> > readers avg (MB/s) 855.0 462.4 -45.9%
> >
> > nfsd-io-bench results on same hardware (XFS on NVMe, NFSv3 via fio
> > NFS engine with libnfs, 1024 NFSD threads, pool_mode=pernode,
> > file size ~502 GB, compared to v6.19-ish baseline):
> >
> > Single-client sequential write (MB/s):
> > baseline patched change
> > buffered 4844.2 4653.4 -3.9%
> > dontcache 3028.3 3723.1 +22.9%
> > direct 957.6 987.8 +3.2%
> >
> > Single-client sequential write p99.9 latency (us):
> > baseline patched change
> > dontcache 759169.0 175112.2 -76.9%
> >
> > Single-client random write (MB/s):
> > baseline patched change
> > dontcache 590.0 1561.0 +164.6%
> >
> > Multi-writer aggregate throughput (MB/s):
> > baseline patched change
> > buffered 9636.3 9422.9 -2.2%
> > dontcache 1894.9 9442.6 +398.3%
> > direct 809.6 975.1 +20.4%
> >
> > Noisy neighbor (dontcache writer + random readers):
> > baseline patched change
> > writer (MB/s) 1854.5 4063.6 +119.1%
> > readers avg (MB/s) 131.2 101.6 -22.5%
> >
> > The NFS results show even larger improvements than the local benchmarks.
> > Multi-writer dontcache throughput improves nearly 5x, matching buffered
> > I/O. Dirty page footprint drops 85-95% in sequential workloads vs.
> > buffered.
> >
>
> Nice :)
> Some explaination here of why 5x improvement with NFS compared to local
> filesystems please?
> (I am not much aware of NFS side, but a possible reasoning would help)
>
I suspect that it's because of the "scattered" nature of nfsd writes.
When the client sends a write to nfsd, we wake a nfsd thread to service
it. So, if there are a lot of writes operating in parallel, they all
get done in the context of different tasks.
My hunch is that this I/O pattern (writing to same file from a bunch of
different threads), particularly suffers from the DONTCACHE inline
write behavior. The threads all end up competing to submit jobs to the
queue and that causes the performance to fall off sharply.
Thanks for the review!
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply
* Re: [PATCH v3 2/4] mm: kick writeback flusher for IOCB_DONTCACHE with targeted dirty tracking
From: Jeff Layton @ 2026-04-27 10:51 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Mike Snitzer, Jens Axboe, Ritesh Harjani, Christoph Hellwig,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Chuck Lever, linux-fsdevel, linux-kernel,
linux-nfs, linux-mm, linux-trace-kernel
In-Reply-To: <ae55M8xBIVYZXPFN@casper.infradead.org>
On Sun, 2026-04-26 at 21:44 +0100, Matthew Wilcox wrote:
> On Sun, Apr 26, 2026 at 07:56:08AM -0400, Jeff Layton wrote:
> > Mixed-mode noisy neighbor (dontcache writer + buffered readers):
> > baseline patched change
> > writer (MB/s) 1297.6 1471.1 +13.4%
> > readers avg (MB/s) 855.0 462.4 -45.9%
>
> hm. This wasn't what I thought of when I thought of "noisy neighbour".
> I'd have process A doing DONTCACHE writes to file A and process B doing
> normal buffered writes to file B.
Originally, I was benchmarking this via nfsd and only later did I add
the suite for local benchmarks. With nfsd, setting the iomode affects
all reads or writes.
So initially, I had it testing them with both reads and writes set to
the same setting, but then later I decided to play with different modes
for reads and writes. The best performing one was buffered reads +
dontcache writes. It's possible a mix of different modes will be better
on a local fs.
I can't easily do a benchmark like you're suggesting with nfsd, but it
should be possible on a local benchmark. I'll see what I can come up
with.
Thanks,
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply
* [PATCH] fprobe: Add unregister_fprobe_sync() for synchronous unregistration
From: Masami Hiramatsu (Google) @ 2026-04-27 12:09 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mathieu Desnoyers, Jonathan Corbet, linux-kernel,
linux-trace-kernel, linux-doc
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Currently, unregister_fprobe() removes the ftrace hooks but does not
wait for the RCU grace period to expire. This is efficient for batch
unregistration of multiple fprobes (to avoid multiple RCU grace period
latencies), but it leaves a window where probe handlers might still be
running on other CPUs after the function returns.
If a caller needs to free the fprobe structure or unload the module
immediately after unregistration, they must manually call
synchronize_rcu() to prevent use-after-free issues.
To simplify this use case, introduce unregister_fprobe_sync(). This
function unregisters the fprobe and waits for the RCU grace period to
complete before returning.
Also, update the documentation of unregister_fprobe() to clarify its
non-blocking behavior and suggest using unregister_fprobe_sync() for the
last probe in a batch. Finally, update the fprobe sample module to use
the synchronous version on exit to ensure safe module unloading.
And add a fix to use synchronous version in the sample code and
trace_fprobe (unexpected error case).
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Documentation/trace/fprobe.rst | 15 ++++++++++++---
include/linux/fprobe.h | 5 +++++
kernel/trace/fprobe.c | 30 ++++++++++++++++++++++++++++++
kernel/trace/trace_fprobe.c | 9 +++++++--
samples/fprobe/fprobe_example.c | 2 +-
5 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/Documentation/trace/fprobe.rst b/Documentation/trace/fprobe.rst
index 95998b189ae3..eee4860ab29a 100644
--- a/Documentation/trace/fprobe.rst
+++ b/Documentation/trace/fprobe.rst
@@ -65,6 +65,12 @@ To disable (remove from functions) this fprobe, call::
unregister_fprobe(&fp);
+Or if you need to wait for the RCU grace period to ensure no handlers
+are running on any CPU (e.g., before freeing the `fprobe` structure),
+use::
+
+ unregister_fprobe_sync(&fp);
+
You can temporally (soft) disable the fprobe by::
disable_fprobe(&fp);
@@ -81,9 +87,12 @@ Same as ftrace, the registered callbacks will start being called some time
after the register_fprobe() is called and before it returns. See
Documentation/trace/ftrace.rst.
-Also, the unregister_fprobe() will guarantee that both enter and exit
-handlers are no longer being called by functions after unregister_fprobe()
-returns as same as unregister_ftrace_function().
+Also, the `unregister_fprobe_sync()` will guarantee that both enter and exit
+handlers are no longer being called by functions after it returns.
+On the other hand, `unregister_fprobe()` does not wait for the RCU grace period,
+so handlers might still be running on other CPUs for a short time after it returns.
+This is useful when you unregister multiple fprobes in a batch to avoid
+waiting for the RCU grace period for each one.
The fprobe entry/exit handler
=============================
diff --git a/include/linux/fprobe.h b/include/linux/fprobe.h
index 0a3bcd1718f3..6ae452e250a1 100644
--- a/include/linux/fprobe.h
+++ b/include/linux/fprobe.h
@@ -94,6 +94,7 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter
int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num);
int register_fprobe_syms(struct fprobe *fp, const char **syms, int num);
int unregister_fprobe(struct fprobe *fp);
+int unregister_fprobe_sync(struct fprobe *fp);
bool fprobe_is_registered(struct fprobe *fp);
int fprobe_count_ips_from_filter(const char *filter, const char *notfilter);
#else
@@ -113,6 +114,10 @@ static inline int unregister_fprobe(struct fprobe *fp)
{
return -EOPNOTSUPP;
}
+static inline int unregister_fprobe_sync(struct fprobe *fp)
+{
+ return -EOPNOTSUPP;
+}
static inline bool fprobe_is_registered(struct fprobe *fp)
{
return false;
diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
index cc49ebd2a773..5f3e48385a47 100644
--- a/kernel/trace/fprobe.c
+++ b/kernel/trace/fprobe.c
@@ -1097,6 +1097,9 @@ static int unregister_fprobe_nolock(struct fprobe *fp)
* @fp: A fprobe data structure to be unregistered.
*
* Unregister fprobe (and remove ftrace hooks from the function entries).
+ * Note: This function does not wait for RCU grace period, since user
+ * may use several fprobes (and then unregister them one by one). In that
+ * case, it is recommended to use unregister_fprobe_sync() for the last fprobe.
*
* Return 0 if @fp is unregistered successfully, -errno if not.
*/
@@ -1110,6 +1113,33 @@ int unregister_fprobe(struct fprobe *fp)
}
EXPORT_SYMBOL_GPL(unregister_fprobe);
+/**
+ * unregister_fprobe_sync() - Unregister fprobe synchronously with RCU grace period.
+ * @fp: A fprobe data structure to be unregistered.
+ *
+ * Unregister fprobe (and remove ftrace hooks from the function entries) and
+ * wait for the RCU grace period to finish. This is useful for preventing
+ * the fprobe from being used after it is unregistered.
+ *
+ * Return 0 if @fp is unregistered successfully, -errno if not.
+ */
+int unregister_fprobe_sync(struct fprobe *fp)
+{
+ int ret;
+
+ guard(mutex)(&fprobe_mutex);
+ if (!fp || !fprobe_registered(fp))
+ return -EINVAL;
+
+ ret = unregister_fprobe_nolock(fp);
+ if (ret)
+ return ret;
+
+ synchronize_rcu();
+ return 0;
+}
+EXPORT_SYMBOL_GPL(unregister_fprobe_sync);
+
static int __init fprobe_initcall(void)
{
rhltable_init(&fprobe_ip_table, &fprobe_rht_params);
diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c
index 9f5f08c0e7c2..fa5b41f7f306 100644
--- a/kernel/trace/trace_fprobe.c
+++ b/kernel/trace/trace_fprobe.c
@@ -845,8 +845,13 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
/* Internal unregister function - just handle fprobe and flags */
static void __unregister_trace_fprobe(struct trace_fprobe *tf)
{
- if (trace_fprobe_is_registered(tf))
- unregister_fprobe(&tf->fp);
+ /*
+ * Here, @tf must NOT be busy, so it MUST be unregistered already.
+ * But if it is unexpectedly registered, unregister it synchronously.
+ */
+ if (WARN_ON_ONCE(trace_fprobe_is_registered(tf)))
+ unregister_fprobe_sync(&tf->fp);
+
if (tf->tuser) {
tracepoint_user_put(tf->tuser);
tf->tuser = NULL;
diff --git a/samples/fprobe/fprobe_example.c b/samples/fprobe/fprobe_example.c
index bfe98ce826f3..382d2f67672a 100644
--- a/samples/fprobe/fprobe_example.c
+++ b/samples/fprobe/fprobe_example.c
@@ -142,7 +142,7 @@ static int __init fprobe_init(void)
static void __exit fprobe_exit(void)
{
- unregister_fprobe(&sample_probe);
+ unregister_fprobe_sync(&sample_probe);
pr_info("fprobe at %s unregistered. %ld times hit, %ld times missed\n",
symbol, nhit, sample_probe.nmissed);
^ permalink raw reply related
* Re: [PATCH 0/9] rtla/tests: Extend runtime test coverage
From: Wander Lairson Costa @ 2026-04-27 12:20 UTC (permalink / raw)
To: Tomas Glozar
Cc: Steven Rostedt, John Kacur, Luis Goncalves, Crystal Wood,
Costa Shulyupin, LKML, linux-trace-kernel
In-Reply-To: <20260423130558.882022-1-tglozar@redhat.com>
On Thu, Apr 23, 2026 at 03:05:49PM +0200, Tomas Glozar wrote:
> This patchset introduces some new tests to cover more options, especially
> histogram and thread options. Most of the new tests use positive and negative
> output matches, sometimes in combination with action scripts, to verify that
> RTLA is applying the settings correctly.
>
> Tests were reorganized a little, adding two new sections: thread tests and
> histogram tests, next to basic tests.
>
> Additionally, coverage of existing tests is extended by adding new matches and
> by extending tests to cover both top and hist tools where possible. For the
> latter, new helpers check_top_hist and check_top_q_hist are added to engine.sh.
>
> As part of the new action scripts, detection of measurement threads is made more
> robust by following child processes of either RTLA (user workload) or kthreadd
> (kernel workload) rather than grepping through the comms of all processes, which
> might have lead to false positives.
>
> These changes significantly improve test coverage and make the test suite more
> against false positives from unrelated processes.
Reviewed-by: Wander Lairson Costa <wander@redhat.com>
>
> Tomas Glozar (9):
> rtla/tests: Cover both top and hist tools where possible
> rtla/tests: Add get_workload_pids() helper
> rtla/tests: Check -c/--cpus thread affinity
> rtla/tests: Use negative match when testing --aa-only
> rtla/tests: Extend timerlat top --aa-only coverage
> rtla/tests: Cover all hist options in runtime tests
> rtla/tests: Add runtime test for -H/--house-keeping
> rtla/tests: Add runtime test for -k and -u options
> rtla/tests: Add runtime tests for -C/--cgroup
>
> tools/tracing/rtla/tests/engine.sh | 15 +++
> tools/tracing/rtla/tests/osnoise.t | 73 +++++++----
> .../rtla/tests/scripts/check-cgroup-match.sh | 17 +++
> .../tracing/rtla/tests/scripts/check-cpus.sh | 9 ++
> .../tests/scripts/check-housekeeping-cpus.sh | 4 +
> .../rtla/tests/scripts/check-priority.sh | 8 +-
> .../scripts/check-user-kernel-threads.sh | 16 +++
> .../tests/scripts/lib/get_workload_pids.sh | 11 ++
> tools/tracing/rtla/tests/timerlat.t | 113 +++++++++++-------
> 9 files changed, 194 insertions(+), 72 deletions(-)
> create mode 100755 tools/tracing/rtla/tests/scripts/check-cgroup-match.sh
> create mode 100755 tools/tracing/rtla/tests/scripts/check-cpus.sh
> create mode 100755 tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh
> create mode 100755 tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh
> create mode 100644 tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh
>
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH v3 2/4] mm: kick writeback flusher for IOCB_DONTCACHE with targeted dirty tracking
From: Jan Kara @ 2026-04-27 12:46 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara,
Matthew Wilcox (Oracle), Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Mike Snitzer, Jens Axboe,
Ritesh Harjani, Christoph Hellwig, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Chuck Lever,
linux-fsdevel, linux-kernel, linux-nfs, linux-mm,
linux-trace-kernel
In-Reply-To: <20260426-dontcache-v3-2-79eb37da9547@kernel.org>
On Sun 26-04-26 07:56:08, Jeff Layton wrote:
> The IOCB_DONTCACHE writeback path in generic_write_sync() calls
> filemap_flush_range() on every write, submitting writeback inline in
> the writer's context. Perf lock contention profiling shows the
> performance problem is not lock contention but the writeback submission
> work itself — walking the page tree and submitting I/O blocks the writer
> for milliseconds, inflating p99.9 latency from 23ms (buffered) to 93ms
> (dontcache).
>
> Replace the inline filemap_flush_range() call with a flusher kick that
> drains dirty pages in the background. This moves writeback submission
> completely off the writer's hot path.
>
> To avoid flushing unrelated buffered dirty data, add a dedicated
> WB_start_dontcache bit and wb_check_start_dontcache() handler that uses
> the new NR_DONTCACHE_DIRTY counter to determine how many pages to write
> back. The flusher writes back that many pages from the oldest dirty
> inodes (not restricted to dontcache-specific inodes). This helps
> preserve I/O batching while limiting the scope of expedited writeback.
>
> Like WB_start_all, the WB_start_dontcache bit coalesces multiple
> DONTCACHE writes into a single flusher wakeup without per-write
> allocations.
>
> Also add WB_REASON_DONTCACHE as a new writeback reason for tracing
> visibility, and target the correct cgroup writeback domain via
> unlocked_inode_to_wb_begin().
>
> dontcache-bench results on dual-socket Xeon Gold 6138 (80 CPUs, 256 GB
> RAM, Samsung MZ1LB1T9HALS 1.7 TB NVMe, local XFS, io_uring, file size
> ~503 GB, compared to a v6.19-ish baseline):
>
> Single-client sequential write (MB/s):
> baseline patched change
> buffered 1449.8 1440.1 -0.7%
> dontcache 1347.9 1461.5 +8.4%
> direct 1450.0 1440.1 -0.7%
>
> Single-client sequential write latency (us):
> baseline patched change
> dontcache p50 3031.0 10551.3 +248.1%
> dontcache p99 74973.2 21626.9 -71.2%
> dontcache p99.9 85459.0 23199.7 -72.9%
>
> Single-client random write (MB/s):
> baseline patched change
> dontcache 284.2 295.4 +3.9%
>
> Single-client random write p99.9 latency (us):
> baseline patched change
> dontcache 2277.4 872.4 -61.7%
>
> Multi-writer aggregate throughput (MB/s):
> baseline patched change
> buffered 1619.5 1611.2 -0.5%
> dontcache 1281.1 1629.4 +27.2%
> direct 1545.4 1609.4 +4.1%
>
> Mixed-mode noisy neighbor (dontcache writer + buffered readers):
> baseline patched change
> writer (MB/s) 1297.6 1471.1 +13.4%
> readers avg (MB/s) 855.0 462.4 -45.9%
>
> nfsd-io-bench results on same hardware (XFS on NVMe, NFSv3 via fio
> NFS engine with libnfs, 1024 NFSD threads, pool_mode=pernode,
> file size ~502 GB, compared to v6.19-ish baseline):
>
> Single-client sequential write (MB/s):
> baseline patched change
> buffered 4844.2 4653.4 -3.9%
> dontcache 3028.3 3723.1 +22.9%
> direct 957.6 987.8 +3.2%
>
> Single-client sequential write p99.9 latency (us):
> baseline patched change
> dontcache 759169.0 175112.2 -76.9%
>
> Single-client random write (MB/s):
> baseline patched change
> dontcache 590.0 1561.0 +164.6%
>
> Multi-writer aggregate throughput (MB/s):
> baseline patched change
> buffered 9636.3 9422.9 -2.2%
> dontcache 1894.9 9442.6 +398.3%
> direct 809.6 975.1 +20.4%
>
> Noisy neighbor (dontcache writer + random readers):
> baseline patched change
> writer (MB/s) 1854.5 4063.6 +119.1%
> readers avg (MB/s) 131.2 101.6 -22.5%
>
> The NFS results show even larger improvements than the local benchmarks.
> Multi-writer dontcache throughput improves nearly 5x, matching buffered
> I/O. Dirty page footprint drops 85-95% in sequential workloads vs.
> buffered.
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
One comment regarding how the writeback is started:
> +static long wb_check_start_dontcache(struct bdi_writeback *wb)
> +{
> + long nr_pages;
> +
> + if (!test_bit(WB_start_dontcache, &wb->state))
> + return 0;
> +
> + nr_pages = global_node_page_state(NR_DONTCACHE_DIRTY);
> + if (nr_pages) {
> + struct wb_writeback_work work = {
> + .nr_pages = wb_split_bdi_pages(wb, nr_pages),
> + .sync_mode = WB_SYNC_NONE,
> + .range_cyclic = 1,
> + .reason = WB_REASON_DONTCACHE,
> + };
> +
> + nr_pages = wb_writeback(wb, &work);
> + }
> +
> + clear_bit(WB_start_dontcache, &wb->state);
> + return nr_pages;
> +}
So this will end up splitting NR_DONTCACHE_DIRTY folios among per-cgroup wb
structures based on their writeback bandwidth. This is a reasonable thing
for global writeback where the bandwidth more or less corresponds to the
amount of dirty folios. However with DONTCACHE I expect big differences in
among NR_DONTCACHE_DIRTY among different cgroups not necessarily
corresponding to wb throughput. In particular if you do DONTCACHE writes
from one cgroup and normal writes from another cgroup this will
systematically underestimate the amount needed to write by a factor of
about two.
So I think the stat should be a per bdi_writeback one (instead of a per node
one) which would avoid the need to split the value to wb here.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply
* Re: [PATCH] Documentation/rv: Replace stale website link
From: Gabriele Monaco @ 2026-04-27 12:56 UTC (permalink / raw)
To: Jonathan Corbet, rdunlap, Steven Rostedt, linux-trace-kernel,
linux-doc, linux-kernel
Cc: matteo.martelli, skhan
In-Reply-To: <87340gpvdg.fsf@trenco.lwn.net>
On Mon, 2026-04-27 at 04:09 -0600, Jonathan Corbet wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
>
> > On Mon, 2026-04-27 at 03:44 -0600, Jonathan Corbet wrote:
> > > Since, as you say, it can be found online, is there a reason not
> > > to include a link here?
> >
> > Mmh, perhaps being overly cautious for the link not to break again?
> >
> > The paper is published so I assume it's always going to be
> > available in some way. It is currently hosted by the university at
> > [1], which may be unlikely to change, and can be found via DOI at
> > [2], which should never change (at least that's what I believe a
> > DOI is for) but brings to the publisher's website rather than the
> > open-access PDF.
> >
> > I think the reference to the paper I included is robust yet easy to
> > use with any scientific or even general purpose search engine. But
> > if you believe using either of the two links is more appropriate, I
> > can send a V2 with the change.
>
> I will defer to others in the end, but to me it seems that we should
> make life easier for our readers whenever we can. Providing a link
> seems better than requiring them to search for it themselves.
Alright, makes sense. I'm going to send a V2 with [1] (the open access
PDF), in the remote case the link stops working, we can update it.
Thanks,
Gabriele
^ permalink raw reply
* Re: [RFC PATCH 00/19] mm/damon: introduce data attributes monitoring
From: Gutierrez Asier @ 2026-04-27 13:16 UTC (permalink / raw)
To: SeongJae Park
Cc: Liam R. Howlett, Andrew Morton, David Hildenbrand,
Jonathan Corbet, Lorenzo Stoakes, Masami Hiramatsu,
Mathieu Desnoyers, Michal Hocko, Mike Rapoport, Shuah Khan,
Shuah Khan, Steven Rostedt, Suren Baghdasaryan, Vlastimil Babka,
damon, linux-doc, linux-kernel, linux-kselftest, linux-mm,
linux-trace-kernel
In-Reply-To: <20260426205222.93895-1-sj@kernel.org>
Hi SeonJae,
On 4/26/2026 11:52 PM, SeongJae Park wrote:
> TL; DR
> ======
>
> Extend DAMON for monitoring general data attributes other than accesses.
> This is for enabling light-weight page type (e.g., belonging cgroup)
> aware monitoring in short term. In long term, this will help extending
> DAMON for multiple access events capture primitives (e.g., page faults
> and PMU) and eventually pivotting DAMON to a "Data Attributes Monitoring
> and Operations eNgine" in long term.
Very interesting. Looking forward to seeing this in upstream.
>
> Background: High Cost of Page Level Properties Monitoring
> =========================================================
>
> DAMON is initially introduced as a Data Access MONitor. It has been
> extended for not only access monitoring but also data access-aware
> system operations (DAMOS). But still the monitoring part is only for
> data accesses.
>
> Data access patterns is good information, but some users need more
> holistic views. Particularly, users want to show the access pattern
> information together with the types of the memory. For example, users
> who work for making huge pages efficiently want to know how much of
> DAMON-found hot/cold regions are backed by huge pages. Users who run
> multiple workloads with different cgroups want to know how much of
> DAMON-found hot/cold regions belong to specific cgroups.
>
> For the user demand, we developed a DAMOS extension for page level
> properties based monitoring [1], which has landed on 6.14. Using the
> feature, users can inform the page level data properties that they are
> interested in, in a flexible format that uses DAMOS filters. Then,
> DAMON applies the filters to each folio of the entire DAMON region and
> lets users know how many bytes of memory in each DAMON region passed the
> given filters.
>
> This gives page level detailed and deterministic information to users.
> But, because the operation is done at page level, the overhead is
> proportional to the memory size. It was useful for test or debugging
> purposes on a small number of machines. But it was obviously too heavy
> to be enabled always on all machines running the real user workloads.
> For real world workloads, it was recommended to use the feature with
> user-space controlled sampling approaches. For example, users could do
> the page level monitoring only once per hour, on randomly selected one
> percent of machines of their fleet. If the runtime and the size of the
> fleet is long and big enough, it should provide statistically meaningful
> data.
>
> But users are too busy to implement such controls on their own.
>
> Data Attributes Monitoring
> ==========================
>
> Extend DAMON to monitor not only data accesses, but also general data
> attributes. Do the extension while keeping the main promise of DAMON,
> the bounded and best-effort minimum overhead.
>
> Allow users to specify what data attributes in addition to the data
> access they want to monitor. Users can install one 'data probe' per
> data attribute of their interest for this purpose. The 'data probe'
> should be able to be applied to any memory, and determine if the given
> memory has the appropriate data attribute. E.g., if memory of physical
> address 42 belongs to cgroup A. Each 'data probe' is configured with
> filters that are very similar to the DAMOS filters.
>
> When DAMON checks if each sampling address memory of each region is
> accessed since the last check, it applies data probes if registered.
> Same to the number of access check-positive samples accounting
> (nr_accesses), it accounts the number of each data probe-positive
> samples in another per-region counters array, namely 'probe_hits'. When
> DAMON resets nr_accesses every aggregation interval, it resets
> 'probe_hits' together.
>
> Users can read 'probe_hits' just before the values are reset. In this
> way, users can know how many hot/cold memory regions have data
> attributes of their interest. E.g., 30 percent of this system's hot
> memory is belonging to cgroup A and 80 percent of the hot cgroup A
> memory is backed by huge pages.
>
> Patches Sequence
> ================
>
> First eight patches implement the core feature, interface and the
> working support. Patch 1 introduces data probe data structure, namely
> damon_probe. Patch 2 extends damon_ctx for installing data probes.
> Patch 3 introduces another data structure for filters of each data
> probe, namely damon_filter. Patch 4 updates damon_ctx commit function
> to handle the probes. Patch 5 extends damon_region for the per-region
> per-probe positive samples counter, namely probe_hits. Patch 6 extends
> damon_operations for applying probes on the underlying DAMON operations
> implementation. Patch 7 updates kdamond_fn() to invoke the probes
> applying callback. Patch 8 finally implements the probes support on
> paddr ops.
>
> Eight changes for user interface (patches 9-16) come next. Patches 9-13
> implements sysfs directories and files for setting data probes, namely
> probes directory, probe directory, filters directory, filter directory
> and filter directory internal files, respectively. Patch 14 connects
> the user inputs that are made via the sysfs files to DAMON core.
> Patch 15 implements sysfs files for showing the per-region per-probe
> positive samples count, namely probe_hits. Patch 16 introduces a new
> tracepoint for showing the counts via tracefs.
>
> Patch 14 adds a selftest for the sysfs files.
>
> Patches 15 and 16 documents the design and usage of the new feature,
> respectively.
>
> Discussions
> ===========
>
> This allows the page properties monitoring with overhead that is low
> enough to be enabled always on real world workloads. Because the
> sampling time for access check is reused for data attributes check, the
> upper-bounded and best-effort minimum overhead of DAMON is kept.
> Because the sampling memory for access check is reused for data
> attributes check, additional overhead is minimum.
>
> Still DAMOS-based page level properties monitoring should be useful,
> because it provides a deterministic page level information. When in
> doubt of the sampling based information, running DAMOS-based one
> together and comparing the results would be useful, for debugging and
> tuning.
>
> Plan for Dropping RFC tag
> =========================
>
> The user ABI for reading probe_hits is not yet convincing. It is
> exposed to users by a tracepoint and new sysfs file. For the
> tracepoint, a new one namely damon:damon_aggregated_v2 is introduced.
> The name is not convincing, and its internal mechanism seems to have
> room to be improved before dropping RFC. For the sysfs, a file under
> the DAMOS-tried region directory namely 'probe_hits' is added. Reading
> it returns four probe_hits values with ',' as a separator. With the
> maximum number of data probes, this should work. This can make future
> changes of the limit difficult. I will try to find a better way before
> dropping the RFC tag. Maybe 'probe_hits/' directory having files of
> name '0' to 'N-1' for each of user-registered 'N' data probes.
>
> I'm currently hoping to drop the RFC tag by 7.2-rc1.
>
> Future Works: Short Term
> ========================
>
> This series is introducing only a single type of data attribute:
> anonymous page. Once this is landed, I will extend it for
> cgroup-belonging, so that we can do cgroup-level monitoring with low
> overhead. After that, I may further work on supporting all DAMOS filter
> types. And as demands are found, we could extend the types.
>
> This version of implementation is limiting the maximum number of data
> probes to four. I will try to find a way to remove the limit in future,
> if it is easy to do. I personally think it should be enough for common
> use cases, though, and therefore not giving high priority at the moment.
>
> Future Works: Long Term
> =======================
>
> There are user requests for extending DAMON with detailed access
> information, for example, per-CPUs/threads/read/writes monitoring. For
> that, I was working [2] on extending DAMON to use page fault events as
> another access check primitives, and making the infrastructure flexible
> for future use of yet another access check primitive. Actually there is
> another ongoing work [3] for extending DAMON with PMU events. The
> motivation of the work is reducing the overhead, though.
>
> In my work [2], I was introducing a new interface for access sampling
> primitives control. Now I think this data probe interface can be used
> for that, too. That is, data access becomes just one type of data
> attribute. Also, pg_idle-confirmed access, page fault-confirmed access,
> and PMU event-confirmed access will be different types of data
> attributes.
>
> The regions adjustment mechanism is currently working based on the
> access information. That's because DAMON is designed for data access
> monitoring. That is, data access information is the primary interest,
> and therefore DAMON adjusts regions in a way that can best-present the
> information.
>
> Once data access becomes just one of data attributes, there is no reason
> to think data access that special. There might be some users not
> interested in access at all but want to know the location of memory of
> specific type. Data probes interface will allow doing that. Further,
> we could extend the interface to let users set any data attribute as the
> 'primary' attribute. Then, DAMON will split and merge regions in a way
> that can best-present the 'primary' attributes.
>
> DAMOS will also be extended, to specify targets based on not only the
> data access pattern, but all user-registered data attributes. From this
> stage, we may be able to call DAMON as a "Data Attributes Monitoring and
> Operations eNgine".
>
> [1] https://lore.kernel.org/20250106193401.109161-1-sj@kernel.org
> [2] https://lore.kernel.org/20251208062943.68824-1-sj@kernel.org/
> [3] https://lore.kernel.org/20260423004211.7037-1-akinobu.mita@gmail.com
>
> SeongJae Park (19):
> mm/damon/core: introduce struct damon_probe
> mm/damon/core: embed damon_probe objects in damon_ctx
> mm/damon/core: introduce damon_filter
> mm/damon/core: commit probes
> mm/damon/core: introduce damon_region->probe_hits
> mm/damon/core: introduce damon_ops->apply_probes
> mm/damon/core: do data attributes monitoring
> mm/damon/paddr: support data attributes monitoring
> mm/damon/sysfs: implement probes dir
> mm/damon/sysfs: implement probe dir
> mm/damon/sysfs: implement filters directory
> mm/damon/sysfs: implement filter dir
> mm/damon/sysfs: implement filter dir files
> mm/damon/sysfs: setup probes on DAMON core API parameters
> mm/damon/sysfs-schemes: implement tried_region/probe_hits file
> mm/damon: trace probe_hits
> selftests/damon/sysfs.sh: test probes dir
> Docs/mm/damon/design: document data attributes monitoring
> Docs/admin-guide/mm/damon/usage: document data attributes monitoring
>
> Documentation/admin-guide/mm/damon/usage.rst | 44 +-
> Documentation/mm/damon/design.rst | 37 ++
> include/linux/damon.h | 60 +++
> include/trace/events/damon.h | 41 ++
> mm/damon/core.c | 182 +++++++
> mm/damon/paddr.c | 45 ++
> mm/damon/sysfs-schemes.c | 30 ++
> mm/damon/sysfs.c | 502 +++++++++++++++++++
> tools/testing/selftests/damon/sysfs.sh | 48 ++
> 9 files changed, 982 insertions(+), 7 deletions(-)
>
>
> base-commit: 8f22aa2e28454419ed2031119ad32ea4a6c9f1f1
My main concern is about potential pollution of sysfs. DAMON is already
complex to set up, with a lot of knobs. Adding more configuration options
may make admin's job more complex.
Do you plan to support this extension in damo user space?
--
Asier Gutierrez
Huawei
^ permalink raw reply
* [PATCH v2] Documentation/rv: Replace stale website link
From: Gabriele Monaco @ 2026-04-27 13:17 UTC (permalink / raw)
To: rdunlap, Steven Rostedt, Gabriele Monaco, Jonathan Corbet,
linux-trace-kernel, linux-doc, linux-kernel
Cc: matteo.martelli, skhan
The sched monitor page was linking to Daniel's website which is now
down. The main purpose of the link was to point to a source for the
models from the original author and that can be found also in his
published paper.
Replace the link with a reference to Daniel's "A thread synchronization
model for the PREEMPT_RT Linux kernel" which can be found online and
includes the models definitions as well as the work behind them (not the
original patches but since they're based on a 5.0 kernel and are mostly
included upstream, there's little value in keeping them in the docs).
Fixes: 03abeaa63c08 ("Documentation/rv: Add docs for the sched monitors")
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
V2: Add link to the PDF and fixed RST references
Documentation/trace/rv/monitor_sched.rst | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/Documentation/trace/rv/monitor_sched.rst b/Documentation/trace/rv/monitor_sched.rst
index 0b96d6e147c6..d3ba7edc202f 100644
--- a/Documentation/trace/rv/monitor_sched.rst
+++ b/Documentation/trace/rv/monitor_sched.rst
@@ -36,7 +36,7 @@ Specifications
--------------
The specifications included in sched are currently a work in progress, adapting the ones
-defined in by Daniel Bristot in [1].
+defined by Daniel Bristot in [1]_.
Currently we included the following:
@@ -365,4 +365,7 @@ constraints when processing the events::
References
----------
-[1] - https://bristot.me/linux-task-model
+.. [1] Daniel Bristot de Oliveira et al.:
+ `A thread synchronization model for the PREEMPT_RT Linux kernel
+ <https://www.iris.sssup.it/bitstream/11382/533630/1/Elsevier-JSA-2020.pdf>`_,
+ J. Syst. Archit., 2020.
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.53.0
^ permalink raw reply related
* Re: [LSF/MM/BPF TOPIC][RFC PATCH v4 00/27] Private Memory Nodes (w/ Compressed RAM)
From: Arun George @ 2026-04-27 12:32 UTC (permalink / raw)
To: Gregory Price
Cc: lsf-pc, linux-kernel, linux-cxl, cgroups, linux-mm,
linux-trace-kernel, damon, kernel-team, gregkh, rafael, dakr,
dave, jonathan.cameron, dave.jiang, alison.schofield,
vishal.l.verma, ira.weiny, dan.j.williams, longman, akpm, david,
lorenzo.stoakes, Liam.Howlett, vbabka, rppt, surenb, mhocko,
osalvador, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, ying.huang, apopple, axelrasmussen, yuanchu, weixugc,
yury.norov, linux, mhiramat, mathieu.desnoyers, tj, hannes,
mkoutny, jackmanb, sj, baolin.wang, npache, ryan.roberts,
dev.jain, baohua, lance.yang, muchun.song, xu.xin16,
chengming.zhou, jannh, linmiaohe, nao.horiguchi, pfalcato,
rientjes, shakeel.butt, riel, harry.yoo, cl, roman.gushchin,
chrisl, kasong, shikemeng, nphamcs, bhe, zhengqi.arch,
terry.bowman, gost.dev, arungeorge05, cpgs
In-Reply-To: <20260222084842.1824063-1-gourry@gourry.net>
[-- Attachment #1: Type: text/plain, Size: 1778 bytes --]
On 22/02/26 03:48AM, Gregory Price wrote:
>Topic type: MM
>
>Presenter: Gregory Price <gourry@gourry.net>
>
>This series introduces N_MEMORY_PRIVATE, a NUMA node state for memory
>managed by the buddy allocator but excluded from normal allocations.
>
>I present it with an end-to-end Compressed RAM service (mm/cram.c)
>that would otherwise not be possible (or would be considerably more
>difficult, be device-specific, and add to the ZONE_DEVICE boondoggle).
>
>
>TL;DR
>===
>
Appreciate the work as we also chase the same problem statement.
A few queries please.
I see the current support relies on read-only mappings which might
limit the performance. Any particular workload you are targeting with
this (which can tolerate this latency)?
Any deployments you think of where the goal is a capacity expansion
with a compromise in performance?
On the device side, are you targeting beyond compressed RAM like
devices such as memory with NAND etc.?
The TL;DR talked about mmap/mbind way of user space allocation from
the private node. But the allocation is controlled by GFP flag
N_MEMORY_PRIVATE. Does the user space path of allocation set this
flag along the way?
And I believe the bear-proof cage might work in the normal scenarios,
but may not work for all. We might not be able to rely on the control
path (backpressure) fully. The control path could go slow, slower and
even die as well. Should the device respond with something like
'bus error' if the host tries to write when it is not capable of
taking any more writes?
Are there any workloads (VM?) where this 'bus error'or similar error
could be an OK / recoverable scenario?
This is assuming that checking with the device on every operation
(whether it is safe to write or not) could be slow.
--- Arun George
[-- Attachment #2: Type: text/plain, Size: 0 bytes --]
^ permalink raw reply
* Re: [PATCH v2 2/2] module/kallsyms: sort function symbols and use binary search
From: Petr Pavlu @ 2026-04-27 13:51 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-modules, Sami Tolvanen, Luis Chamberlain, linux-kernel,
linux-trace-kernel, live-patching, Daniel Gomez, Aaron Tomlin,
Steven Rostedt, Masami Hiramatsu, Jordan Rome, Viktor Malik
In-Reply-To: <20260424091330.GA31168@wp.pl>
On 4/24/26 11:13 AM, Stanislaw Gruszka wrote:
> On Thu, Apr 23, 2026 at 04:00:04PM +0200, Petr Pavlu wrote:
>> On 3/27/26 12:00 PM, Stanislaw Gruszka wrote:
[...]
>>> diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
>>> index f23126d804b2..d69e99e67707 100644
>>> --- a/kernel/module/kallsyms.c
>>> +++ b/kernel/module/kallsyms.c
>>> @@ -10,6 +10,7 @@
>>> #include <linux/kallsyms.h>
>>> #include <linux/buildid.h>
>>> #include <linux/bsearch.h>
>>> +#include <linux/sort.h>
>>> #include "internal.h"
>>>
>>> /* Lookup exported symbol in given range of kernel_symbols */
>>> @@ -103,6 +104,95 @@ static bool is_core_symbol(const Elf_Sym *src, const Elf_Shdr *sechdrs,
>>> return true;
>>> }
>>>
>>> +static inline bool is_func_symbol(const Elf_Sym *sym)
>>> +{
>>> + return sym->st_shndx != SHN_UNDEF && sym->st_size != 0 &&
>>> + ELF_ST_TYPE(sym->st_info) == STT_FUNC;
>>> +}
>>> +
>>> +static unsigned int bsearch_func_symbol(struct mod_kallsyms *kallsyms,
>>> + unsigned long addr,
>>> + unsigned long *bestval,
>>> + unsigned long *nextval)
>>> +
>>> +{
>>> + unsigned int mid, low = 1, high = kallsyms->num_func_syms + 1;
>>> + unsigned int best = 0;
>>> + unsigned long thisval;
>>> +
>>> + while (low < high) {
>>> + mid = low + (high - low) / 2;
>>> + thisval = kallsyms_symbol_value(&kallsyms->symtab[mid]);
>>> +
>>> + if (thisval <= addr) {
>>> + *bestval = thisval;
>>> + best = mid;
>>> + low = mid + 1;
>>
>> If thisval == addr, the search moves to the right and finds the last
>> symbol with the same address. I believe it should do the opposite and
>> return the first symbol to match the behavior of
>> search_kallsyms_symbol().
>
> In the case of multiple symbols sharing the same address, we have
> to pick one and ignore the others. I don’t think it matters much which
> one is chosen in practice. Also, I expect function symbol addresses
> to be unique, so this shouldn’t be a real issue.
I think that the code should consistently pick the same answer. If
someone uses aliases for their functions, the logic shouldn't
arbitrarily return one of them, but preferably the first one, which
should normally be the actual implementation.
>
>>> + } else {
>>> + *nextval = thisval;
>>> + high = mid;
>>> + }
>>> + }
>>> +
>>> + return best;
>>> +}
>>> +
>>> +static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms,
>>> + unsigned int symnum)
>>> +{
>>> + return kallsyms->strtab + kallsyms->symtab[symnum].st_name;
>>> +}
>>> +
>>> +static unsigned int search_kallsyms_symbol(struct mod_kallsyms *kallsyms,
>>> + unsigned long addr,
>>> + unsigned long *bestval,
>>> + unsigned long *nextval)
>>> +{
>>> + unsigned int i, best = 0;
>>> +
>>> + /*
>>> + * Scan for closest preceding symbol and next symbol. (ELF starts
>>> + * real symbols at 1). Skip the initial function symbols range
>>> + * if num_func_syms is non-zero, those are handled separately for
>>> + * the core TEXT segment lookup.
>>> + */
>>> + for (i = 1 + kallsyms->num_func_syms; i < kallsyms->num_symtab; i++) {
>>> + const Elf_Sym *sym = &kallsyms->symtab[i];
>>> + unsigned long thisval = kallsyms_symbol_value(sym);
>>> +
>>> + if (sym->st_shndx == SHN_UNDEF)
>>> + continue;
>>> +
>>> + /*
>>> + * We ignore unnamed symbols: they're uninformative
>>> + * and inserted at a whim.
>>> + */
>>> + if (*kallsyms_symbol_name(kallsyms, i) == '\0' ||
>>> + is_mapping_symbol(kallsyms_symbol_name(kallsyms, i)))
>>> + continue;
>>> +
>>> + if (thisval <= addr && thisval > *bestval) {
>>> + best = i;
>>> + *bestval = thisval;
>>> + }
>>> + if (thisval > addr && thisval < *nextval)
>>> + *nextval = thisval;
>>> + }
>>> +
>>> + return best;
>>> +}
>>> +
>>> +static int elf_sym_cmp(const void *a, const void *b)
>>> +{
>>> + unsigned long val_a = kallsyms_symbol_value((const Elf_Sym *)a);
>>> + unsigned long val_b = kallsyms_symbol_value((const Elf_Sym *)b);
>>> +
>>> + if (val_a < val_b)
>>> + return -1;
>>> +
>>> + return val_a > val_b;
>>
>> Does this comparison function and the sort() call result in stable
>> sorting? If val_a and val_b are the same, the sorting should preserve
>> the original order.
>
> The kernel’s sort() implementation is not stable.
Ok, I see it is a heapsort. It would require additional data to keep
information about the original indexes for elf_sym_cmp() to use as
a tiebreaker.
>
>>> +}
>>> +
>>> /*
>>> * We only allocate and copy the strings needed by the parts of symtab
>>> * we keep. This is simple, but has the effect of making multiple
>>> @@ -115,9 +205,10 @@ void layout_symtab(struct module *mod, struct load_info *info)
>>> Elf_Shdr *symsect = info->sechdrs + info->index.sym;
>>> Elf_Shdr *strsect = info->sechdrs + info->index.str;
>>> const Elf_Sym *src;
>>> - unsigned int i, nsrc, ndst, strtab_size = 0;
>>> + unsigned int i, nsrc, ndst, nfunc, strtab_size = 0;
>>> struct module_memory *mod_mem_data = &mod->mem[MOD_DATA];
>>> struct module_memory *mod_mem_init_data = &mod->mem[MOD_INIT_DATA];
>>> + bool is_lp_mod = is_livepatch_module(mod);
>>>
>>> /* Put symbol section at end of init part of module. */
>>> symsect->sh_flags |= SHF_ALLOC;
>>> @@ -129,12 +220,14 @@ void layout_symtab(struct module *mod, struct load_info *info)
>>> nsrc = symsect->sh_size / sizeof(*src);
>>>
>>> /* Compute total space required for the core symbols' strtab. */
>>> - for (ndst = i = 0; i < nsrc; i++) {
>>> - if (i == 0 || is_livepatch_module(mod) ||
>>> + for (ndst = nfunc = i = 0; i < nsrc; i++) {
>>> + if (i == 0 || is_lp_mod ||
>>> is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum,
>>> info->index.pcpu)) {
>>> strtab_size += strlen(&info->strtab[src[i].st_name]) + 1;
>>> ndst++;
>>> + if (!is_lp_mod && is_func_symbol(src + i))
>>> + nfunc++;
>>> }
>>> }
>>>
>>> @@ -156,6 +249,7 @@ void layout_symtab(struct module *mod, struct load_info *info)
>>> mod_mem_init_data->size = ALIGN(mod_mem_init_data->size,
>>> __alignof__(struct mod_kallsyms));
>>> info->mod_kallsyms_init_off = mod_mem_init_data->size;
>>> + info->num_func_syms = nfunc;
>>>
>>> mod_mem_init_data->size += sizeof(struct mod_kallsyms);
>>> info->init_typeoffs = mod_mem_init_data->size;
>>> @@ -169,7 +263,7 @@ void layout_symtab(struct module *mod, struct load_info *info)
>>> */
>>> void add_kallsyms(struct module *mod, const struct load_info *info)
>>> {
>>> - unsigned int i, ndst;
>>> + unsigned int i, di, nfunc, ndst;
>>> const Elf_Sym *src;
>>> Elf_Sym *dst;
>>> char *s;
>>> @@ -178,6 +272,7 @@ void add_kallsyms(struct module *mod, const struct load_info *info)
>>> void *data_base = mod->mem[MOD_DATA].base;
>>> void *init_data_base = mod->mem[MOD_INIT_DATA].base;
>>> struct mod_kallsyms *kallsyms;
>>> + bool is_lp_mod = is_livepatch_module(mod);
>>>
>>> kallsyms = init_data_base + info->mod_kallsyms_init_off;
>>
>> This code is followed by the initialization of kallsyms:
>>
>> kallsyms->symtab = (void *)symsec->sh_addr;
>> kallsyms->num_symtab = symsec->sh_size / sizeof(Elf_Sym);
>> /* Make sure we get permanent strtab: don't use info->strtab. */
>> kallsyms->strtab = (void *)info->sechdrs[info->index.str].sh_addr;
>> kallsyms->typetab = init_data_base + info->init_typeoffs;
>>
>> I suggest adding 'kallsyms->num_func_syms = 0;' after the initialization
>> of kallsyms->num_symtab.
>
> I relied on zeroed memory initialization, but I can add this explicitly
> for clarity.
>
>>> @@ -194,19 +289,28 @@ void add_kallsyms(struct module *mod, const struct load_info *info)
>>> mod->core_kallsyms.symtab = dst = data_base + info->symoffs;
>>> mod->core_kallsyms.strtab = s = data_base + info->stroffs;
>>> mod->core_kallsyms.typetab = data_base + info->core_typeoffs;
>>> +
>>> strtab_size = info->core_typeoffs - info->stroffs;
>>> src = kallsyms->symtab;
>>> - for (ndst = i = 0; i < kallsyms->num_symtab; i++) {
>>> + ndst = info->num_func_syms + 1;
>>> +
>>> + for (nfunc = i = 0; i < kallsyms->num_symtab; i++) {
>>> kallsyms->typetab[i] = elf_type(src + i, info);
>>> - if (i == 0 || is_livepatch_module(mod) ||
>>> + if (i == 0 || is_lp_mod ||
>>> is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum,
>>> info->index.pcpu)) {
>>> ssize_t ret;
>>>
>>> - mod->core_kallsyms.typetab[ndst] =
>>> - kallsyms->typetab[i];
>>> - dst[ndst] = src[i];
>>> - dst[ndst++].st_name = s - mod->core_kallsyms.strtab;
>>> + if (i == 0)
>>> + di = 0;
>>> + else if (!is_lp_mod && is_func_symbol(src + i))
>>> + di = 1 + nfunc++;
>>> + else
>>> + di = ndst++;
>>> +
>>> + mod->core_kallsyms.typetab[di] = kallsyms->typetab[i];
>>> + dst[di] = src[i];
>>> + dst[di].st_name = s - mod->core_kallsyms.strtab;
>>> ret = strscpy(s, &kallsyms->strtab[src[i].st_name],
>>> strtab_size);
>>> if (ret < 0)
>>> @@ -216,9 +320,13 @@ void add_kallsyms(struct module *mod, const struct load_info *info)
>>> }
>>> }
>>>
>>> + WARN_ON_ONCE(nfunc != info->num_func_syms);
>>> + sort(dst + 1, nfunc, sizeof(Elf_Sym), elf_sym_cmp, NULL);
>>> +
>>
>> The code sorts mod->core_kallsyms.symtab but mod->core_kallsyms.typetab
>> is not reordered accordingly.
>
> Right, but for function symbols the typetab entries are all 't',
> so swapping them does not change the type value. The 'T' vs 't'
> distinction is handled later when printing (based on export status).
> But the comment explaining skiping adjusting of
> mod->core_kallsyms.typetab is needed.
Modules can also contain weak functions with elf_type() = 'w'.
>
>>> /* Set up to point into init section. */
>>> rcu_assign_pointer(mod->kallsyms, kallsyms);
>>> mod->core_kallsyms.num_symtab = ndst;
>>> + mod->core_kallsyms.num_func_syms = nfunc;
>>> }
>>>
>>> #if IS_ENABLED(CONFIG_STACKTRACE_BUILD_ID)
>>> @@ -241,11 +349,6 @@ void init_build_id(struct module *mod, const struct load_info *info)
>>> }
>>> #endif
>>>
>>> -static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned int symnum)
>>> -{
>>> - return kallsyms->strtab + kallsyms->symtab[symnum].st_name;
>>> -}
>>> -
>>> /*
>>> * Given a module and address, find the corresponding symbol and return its name
>>> * while providing its size and offset if needed.
>>> @@ -255,7 +358,10 @@ static const char *find_kallsyms_symbol(struct module *mod,
>>> unsigned long *size,
>>> unsigned long *offset)
>>> {
>>> - unsigned int i, best = 0;
>>> + unsigned int (*search)(struct mod_kallsyms *kallsyms,
>>> + unsigned long addr, unsigned long *bestval,
>>> + unsigned long *nextval);
>>> + unsigned int best;
>>> unsigned long nextval, bestval;
>>> struct mod_kallsyms *kallsyms = rcu_dereference(mod->kallsyms);
>>> struct module_memory *mod_mem = NULL;
>>> @@ -266,6 +372,11 @@ static const char *find_kallsyms_symbol(struct module *mod,
>>> continue;
>>> #endif
>>> if (within_module_mem_type(addr, mod, type)) {
>>> + if (type == MOD_TEXT && kallsyms->num_func_syms > 0)
>>> + search = bsearch_func_symbol;
>>
>> I'm not sure if it is ok to limit the search only to function symbols
>> when the address lies in MOD_TEXT. The text can theoretically contain
>> non-function symbols.
>
> Yes, the patch assumes that the only valid symbols in the MOD_TEXT
> are functions. If there are defined OBJECT symbols in .text, the patch
> would break lookup for those.
>
> While it’s theoretically possible (e.g. hand-written assembly placing
> data in .text ?), I’m not sure this is a practical concern. In general,
> having data in executable segments is discouraged for security reasons.
>
>> Could this optimization be adjusted to sort all
>> MOD_TEXT symbols (excluding anonymous and mapping symbols) and move them
>> to the front of the symbol table?
>
> That’s possible. We could track .text sections indices in
> __layout_sections() and include all valid symbols from those sections,
> and also reorder typetab accordingly.
>
> However, this adds complexity. I would prefer to first confirm whether
> OBJECT symbols in MOD_TEXT is a real issue before going in that direction.
I'm not aware of specific OBJECT symbols that end up in MOD_TEXT.
Nonetheless, it is a valid case and it is preferable that an
optimization doesn't break their lookup by address.
In general, I'm worried about the several edge cases and inconsistencies
that this optimization introduces. This also includes the fact that it
doesn't work for livepatch modules.
An alternative could be to keep the symbol table untouched and have
a separate array with symbol indexes that is sorted by their addresses,
but it requires evaluation if the additional memory usage is worth it.
--
Thanks,
Petr
^ permalink raw reply
* [RFC PATCH 00/12] rv: Add selftests to tools and KUnit tests
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel
Cc: Gabriele Monaco, Steven Rostedt, Nam Cao, Thomas Weissschuh,
Tomas Glozar, John Kacur, Wen Yang
This series adds support to the make check target in the rv userspace
tool and the rvgen script, this allows to quickly validate its
functionality. The selftest framework is inspired by the one used in
RTLA.
A few bugs in both tools were also discovered and are fixed as part of
this series.
Additionally it adds unit tests for models. This is achieved by running
the handlers functions directly within KUnit, emulating all modules
paths as if real kernel events fired.
Unit tests emulate a series of events that are expected to trigger
violations and checks that a reaction occurred, stub structs and
functions are used so the kernel is not affected by the test.
To: linux-trace-kernel@vger.kernel.org
To: linux-kernel@vger.kernel.org
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Nam Cao <namcao@linutronix.de>
Cc: Thomas Weissschuh <thomas.weissschuh@linutronix.de>
Cc: Tomas Glozar <tglozar@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: Wen Yang <wen.yang@linux.dev>
Gabriele Monaco (12):
tools/rv: Fix substring match bug in monitor name search
tools/rv: Fix substring match when listing container monitors
tools/rv: Fix exit status when monitor execution fails
tools/rv: Fix cleanup after failed trace setup
tools/rv: Add selftests
verification/rvgen: Fix options shared among commands
verification/rvgen: Add golden and spec folders for tests
verification/rvgen: Add selftests
rv: Add KUnit stub to rv_react() and rv_*_task_monitor_slot()
rv: Add KUnit tests for some DA/HA monitors
rv: Add KUnit stubs for current and smp_processor_id()
rv: Add KUnit tests for some LTL monitors
include/rv/da_monitor.h | 32 +++
include/rv/kunit_stubs.h | 17 ++
include/rv/ltl_monitor.h | 32 +++
kernel/trace/rv/Kconfig | 14 +
kernel/trace/rv/Makefile | 3 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 30 +++
kernel/trace/rv/monitors/opid/opid.c | 27 ++
.../trace/rv/monitors/pagefault/pagefault.c | 26 +-
kernel/trace/rv/monitors/sco/sco.c | 23 ++
kernel/trace/rv/monitors/sleep/sleep.c | 64 ++++-
kernel/trace/rv/monitors/sssw/sssw.c | 27 ++
kernel/trace/rv/monitors/sts/sts.c | 35 +++
kernel/trace/rv/rv.c | 5 +
kernel/trace/rv/rv_monitors_test.c | 99 +++++++
kernel/trace/rv/rv_monitors_test.h | 90 +++++++
kernel/trace/rv/rv_reactors.c | 3 +
tools/verification/rv/Makefile | 5 +-
tools/verification/rv/src/in_kernel.c | 58 ++--
tools/verification/rv/src/rv.c | 2 +-
tools/verification/rv/tests/rv_list.t | 48 ++++
tools/verification/rv/tests/rv_mon.t | 95 +++++++
tools/verification/rvgen/Makefile | 4 +
tools/verification/rvgen/__main__.py | 10 +-
.../rvgen/tests/golden/da_global/Kconfig | 9 +
.../rvgen/tests/golden/da_global/da_global.c | 95 +++++++
.../rvgen/tests/golden/da_global/da_global.h | 47 ++++
.../tests/golden/da_global/da_global_trace.h | 15 ++
.../tests/golden/da_perobj_parent/Kconfig | 11 +
.../da_perobj_parent/da_perobj_parent.c | 110 ++++++++
.../da_perobj_parent/da_perobj_parent.h | 64 +++++
.../da_perobj_parent/da_perobj_parent_trace.h | 15 ++
.../tests/golden/da_pertask_desc/Kconfig | 9 +
.../golden/da_pertask_desc/da_pertask_desc.c | 105 ++++++++
.../golden/da_pertask_desc/da_pertask_desc.h | 64 +++++
.../da_pertask_desc/da_pertask_desc_trace.h | 15 ++
.../rvgen/tests/golden/ha_percpu/Kconfig | 9 +
.../rvgen/tests/golden/ha_percpu/ha_percpu.c | 244 +++++++++++++++++
.../rvgen/tests/golden/ha_percpu/ha_percpu.h | 72 +++++
.../tests/golden/ha_percpu/ha_percpu_trace.h | 19 ++
.../rvgen/tests/golden/ltl_pertask/Kconfig | 9 +
.../tests/golden/ltl_pertask/ltl_pertask.c | 107 ++++++++
.../tests/golden/ltl_pertask/ltl_pertask.h | 108 ++++++++
.../golden/ltl_pertask/ltl_pertask_trace.h | 14 +
.../rvgen/tests/golden/test_container/Kconfig | 5 +
.../golden/test_container/test_container.c | 35 +++
.../golden/test_container/test_container.h | 3 +
.../rvgen/tests/golden/test_da/Kconfig | 9 +
.../rvgen/tests/golden/test_da/test_da.c | 95 +++++++
.../rvgen/tests/golden/test_da/test_da.h | 47 ++++
.../tests/golden/test_da/test_da_trace.h | 15 ++
.../rvgen/tests/golden/test_ha/Kconfig | 9 +
.../rvgen/tests/golden/test_ha/test_ha.c | 247 ++++++++++++++++++
.../rvgen/tests/golden/test_ha/test_ha.h | 72 +++++
.../tests/golden/test_ha/test_ha_trace.h | 19 ++
.../rvgen/tests/golden/test_ltl/Kconfig | 11 +
.../rvgen/tests/golden/test_ltl/test_ltl.c | 108 ++++++++
.../rvgen/tests/golden/test_ltl/test_ltl.h | 108 ++++++++
.../tests/golden/test_ltl/test_ltl_trace.h | 14 +
.../rvgen/tests/rvgen_container.t | 20 ++
.../verification/rvgen/tests/rvgen_monitor.t | 87 ++++++
.../rvgen/tests/specs/test_da.dot | 16 ++
.../rvgen/tests/specs/test_da2.dot | 18 ++
.../rvgen/tests/specs/test_ha.dot | 27 ++
.../rvgen/tests/specs/test_invalid.dot | 8 +
.../rvgen/tests/specs/test_invalid.ltl | 1 +
.../rvgen/tests/specs/test_invalid_ha.dot | 16 ++
.../rvgen/tests/specs/test_ltl.ltl | 1 +
tools/verification/tests/engine.sh | 156 +++++++++++
68 files changed, 2993 insertions(+), 44 deletions(-)
create mode 100644 include/rv/kunit_stubs.h
create mode 100644 kernel/trace/rv/rv_monitors_test.c
create mode 100644 kernel/trace/rv/rv_monitors_test.h
create mode 100644 tools/verification/rv/tests/rv_list.t
create mode 100644 tools/verification/rv/tests/rv_mon.t
create mode 100644 tools/verification/rvgen/tests/golden/da_global/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.c
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.h
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_container/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.c
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
create mode 100644 tools/verification/rvgen/tests/rvgen_container.t
create mode 100644 tools/verification/rvgen/tests/rvgen_monitor.t
create mode 100644 tools/verification/rvgen/tests/specs/test_da.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_da2.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.ltl
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ltl.ltl
create mode 100644 tools/verification/tests/engine.sh
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.53.0
^ permalink raw reply
* [RFC PATCH 01/12] tools/rv: Fix substring match bug in monitor name search
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
__ikm_find_monitor_name() relies on strstr() to find a monitor by name,
which fails if the target monitor is a substring of a previously listed
monitor.
Fix it by tokenizing the available_monitors file and matching full
tokens instead.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/in_kernel.c | 48 ++++++++++++++-------------
1 file changed, 25 insertions(+), 23 deletions(-)
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index 4bb746ea6e17..a200b63f4509 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -58,38 +58,40 @@ static int __ikm_read_enable(char *monitor_name)
*/
static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
{
- char *available_monitors, container[MAX_DA_NAME_LEN+1], *cursor, *end;
- int retval = 1;
+ char *available_monitors, *cursor, *line;
+ int len = strlen(monitor_name);
+ int found = 0;
available_monitors = tracefs_instance_file_read(NULL, "rv/available_monitors", NULL);
if (!available_monitors)
return -1;
- cursor = strstr(available_monitors, monitor_name);
- if (!cursor) {
- retval = 0;
- goto out_free;
- }
+ config_is_container = 0;
+ cursor = available_monitors;
+ while ((line = strsep(&cursor, "\n"))) {
+ char *colon = strchr(line, ':');
- for (; cursor > available_monitors; cursor--)
- if (*(cursor-1) == '\n')
- break;
- end = strstr(cursor, "\n");
- memcpy(out_name, cursor, end-cursor);
- out_name[end-cursor] = '\0';
-
- cursor = strstr(out_name, ":");
- if (cursor)
- *cursor = '/';
- else {
- sprintf(container, "%s:", monitor_name);
- if (strstr(available_monitors, container))
- config_is_container = 1;
+ if (strcmp(line, monitor_name) && (!colon || strcmp(colon + 1, monitor_name)))
+ continue;
+
+ strncpy(out_name, line, 2 * MAX_DA_NAME_LEN);
+ out_name[2 * MAX_DA_NAME_LEN - 1] = '\0';
+
+ if (colon) {
+ out_name[colon - line] = '/';
+ } else {
+ /* If there are children, they are on the next line. */
+ line = strsep(&cursor, "\n");
+ if (line && !strncmp(line, monitor_name, len) && line[len] == ':')
+ config_is_container = 1;
+ }
+
+ found = 1;
+ break;
}
-out_free:
free(available_monitors);
- return retval;
+ return found;
}
/*
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 02/12] tools/rv: Fix substring match when listing container monitors
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
When listing monitors within a specific container (rv list <container>),
the tool incorrectly matched monitors if the requested container name
was only a prefix of the actual container (e.g., 'rv list sche' would
incorrectly list monitors from 'sched:').
Fix this by ensuring the container name is an exact match and is
immediately followed by the ':' separator.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/in_kernel.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index a200b63f4509..95adae321c11 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -193,8 +193,12 @@ static int ikm_fill_monitor_definition(char *name, struct monitor *ikm, char *co
nested_name = strstr(name, ":");
if (nested_name) {
/* it belongs in container if it starts with "container:" */
- if (container && strstr(name, container) != name)
- return 1;
+ if (container) {
+ int len = strlen(container);
+
+ if (strncmp(name, container, len) || name[len] != ':')
+ return 1;
+ }
*nested_name = '/';
++nested_name;
ikm->nested = 1;
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 03/12] tools/rv: Fix exit status when monitor execution fails
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
When running "rv mon" on a monitor that is already enabled, the tool
fails to start but incorrectly exits with a success status (0).
Fix the exit condition to ensure it returns a failure code on any
execution error.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/rv.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index b8fe24a87d97..6d65f4037581 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -127,7 +127,7 @@ static void rv_mon(int argc, char **argv)
if (!run)
err_msg("rv: monitor %s does not exist\n", monitor_name);
- exit(!run);
+ exit(run <= 0);
}
static void usage(int exit_val, const char *fmt, ...)
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 04/12] tools/rv: Fix cleanup after failed trace setup
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Currently if ikm_setup_trace_instance() fails, the tool returns without
any cleanup, if rv was called with both -t and -r, this means the
reactor is not going to be cleared.
Jump to the cleanup label to restore the reactor if necessary.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/in_kernel.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index 95adae321c11..131a6787d639 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -809,7 +809,7 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
if (config_trace) {
inst = ikm_setup_trace_instance(nested_name);
if (!inst)
- return -1;
+ goto out_free_instance;
}
retval = ikm_enable(full_name);
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 05/12] tools/rv: Add selftests
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
The rv tool needs automated testing to catch regressions and verify
correct functionality across different usage scenarios.
Add selftests that validate monitor listing (including containers and
nested monitors), monitor execution with different configurations
(reactors, verbose output, tracing), and trace output format for both
per-task and per-cpu monitors. Error handling paths are also tested.
Tests use a shared engine for common patterns.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/Makefile | 5 +-
tools/verification/rv/tests/rv_list.t | 48 ++++++++++
tools/verification/rv/tests/rv_mon.t | 95 ++++++++++++++++++++
tools/verification/tests/engine.sh | 124 ++++++++++++++++++++++++++
4 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 tools/verification/rv/tests/rv_list.t
create mode 100644 tools/verification/rv/tests/rv_mon.t
create mode 100644 tools/verification/tests/engine.sh
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 5b898360ba48..8ae5fc0d1d17 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -78,4 +78,7 @@ clean: doc_clean fixdep-clean
$(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-*
$(Q)rm -rf feature
-.PHONY: FORCE clean
+check: $(RV)
+ RV=$(RV) prove -o --directives -f tests/
+
+.PHONY: FORCE clean check
diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t
new file mode 100644
index 000000000000..201af33a52cc
--- /dev/null
+++ b/tools/verification/rv/tests/rv_list.t
@@ -0,0 +1,48 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify help page" \
+ "$RV --help" 0 "usage: rv command"
+
+check "verify list subcommand help" \
+ "$RV list --help" 0 "list all available monitors"
+
+all_nested=$(grep : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+all_non_nested=$(grep -v : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+sched_monitors=$(grep sched: $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+description_state="[[:space:]]\+[[:print:]]\+\[\(OFF\|ON\)\]"
+line_nested=" - \($all_nested\)${description_state}"
+line_non_nested="\($all_non_nested\)${description_state}"
+
+# List monitors and containers
+check "list all monitors" \
+ "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$"
+
+check_if_exists "list container" \
+ "$RV list sched" "$RVDIR/monitors/sched" \
+ "" "-- No monitor found in container sched --" \
+ "^\($sched_monitors\)${description_state}$"
+
+check_if_exists "list non-container" \
+ "$RV list wwnr" "$RVDIR/monitors/wwnr" \
+ "-- No monitor found in container wwnr --" \
+ "^\( - \)\?[[:alnum:]]\+${description_state}$"
+
+check "list incomplete container name" \
+ "$RV list s" 0 "-- No monitor found in container s --"
+
+# Error handling tests
+check "no command" \
+ "$RV" 1 "rv requires a command"
+
+check "invalid command" \
+ "$RV invalid" 1 "rv does not know the invalid command"
+
+test_end
diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
new file mode 100644
index 000000000000..cbc346c74c71
--- /dev/null
+++ b/tools/verification/rv/tests/rv_mon.t
@@ -0,0 +1,95 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify mon subcommand help" \
+ "$RV mon --help" 0 "run a monitor"
+
+# Error handling tests
+check "mon without monitor name" \
+ "$RV mon" 1 "usage: rv mon"
+
+check "invalid monitor name" \
+ "$RV mon invalid" 1 "monitor invalid does not exist"
+
+if [ -d $RVDIR/monitors/wwnr ]; then
+
+check "invalid reactor name" \
+ "$RV mon wwnr -r invalid" 1 "failed to set invalid reactor, is it available?"
+
+check "monitor name is substring of another monitor" \
+ "$RV mon nr" 1 "monitor nr does not exist"
+
+check "already enabled monitor returns error" \
+ "echo 1 > $RVDIR/monitors/wwnr/enable; $RV mon wwnr" 1 \
+ "monitor wwnr (in-kernel) is already enabled"
+echo 0 > $RVDIR/monitors/wwnr/enable
+
+fi
+
+# rv mon runs until terminated
+set_expected_timeout 2s
+
+# Run monitors with different configurations
+check_if_exists "run the monitor without parameters" \
+ "$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "."
+
+check_if_exists "run the monitor as verbose" \
+ "$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \
+ "my pid is \$pid" "\(event\|error\)"
+
+check_if_exists "run the monitor with a reactor" \
+ "$RV mon wwnr -r printk & sleep .5 && cat $RVDIR/monitors/wwnr/reactors && wait" \
+ "$RVDIR/monitors/wwnr/reactors" "\[printk\]"
+
+check_if_exists "reactor is restored after exit" \
+ "cat $RVDIR/monitors/wwnr/reactors" \
+ "$RVDIR/monitors/wwnr/reactors" "\[nop\]"
+
+check_if_exists "run a nested monitor with a reactor" \
+ "$RV mon snroc -r printk & sleep .5 && cat $RVDIR/monitors/sched/snroc/reactors && wait" \
+ "$RVDIR/monitors/sched/snroc/reactors" "\[printk\]"
+
+check_if_exists "run an explicitly nested monitor with a reactor" \
+ "$RV mon sched:sssw -r printk & sleep .5 && cat $RVDIR/monitors/sched/sssw/reactors && wait" \
+ "$RVDIR/monitors/sched/sssw/reactors" "\[printk\]"
+
+check_if_exists "run container monitor" \
+ "$RV mon sched & sleep .5 && cat $RVDIR/monitors/sched/{sssw,sco}/enable && wait" \
+ "$RVDIR/monitors/sched" "1" "0" "^1$"
+
+# Regexes for the trace
+header="^[[:space:]]\+\(\([][A-Z_x<>-]\+\||\)[[:space:]]*\)\+$"
+type="\(event\|error\)[[:space:]]\+"
+genpid="[0-9]\+[[:space:]]\+"
+selfpid="\$pid[[:space:]]\+"
+cpu="\[[0-9]\{3\}\][[:space:]]\+"
+state="[a-z_]\+ "
+trace_task="${genpid}${cpu}${type}${genpid}${state}"
+trace_task_self="${genpid}${cpu}${type}${selfpid}${state}"
+trace_cpu="${genpid}${cpu}${type}${state}"
+trace_cpu_self="${selfpid}${cpu}${type}${state}"
+
+check_if_exists "run per-task monitor with tracing" \
+ "$RV mon sssw -t" "$RVDIR/monitors/sched/sssw" \
+ "$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task monitor tracing also self" \
+ "$RV mon sched:sssw -t -s" "$RVDIR/monitors/sched/sssw" \
+ "$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu monitor with tracing" \
+ "$RV mon sched:sco -t" "$RVDIR/monitors/sched/sco" \
+ "$header" "$trace_cpu_self" "\($header\|$trace_cpu\)"
+
+check_if_exists "run per-cpu monitor tracing also self" \
+ "$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \
+ "$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
+test_end
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
new file mode 100644
index 000000000000..cd367dabef53
--- /dev/null
+++ b/tools/verification/tests/engine.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+test_begin() {
+ # Count tests to allow the test harness to double-check if all were
+ # included correctly.
+ ctr=0
+ [ -z "$RV" ] && RV="../rv/rv"
+ [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
+}
+
+failure() {
+ fail=1
+ if [ $# -gt 0 ]; then
+ failbuf+="$1"
+ failbuf+=$'\n'
+ fi
+}
+
+report() {
+ local desc="$1"
+
+ if [ "$fail" -eq 0 ]; then
+ echo "ok $ctr - $desc"
+ else
+ # Add output and exit code as comments in case of failure
+ echo "not ok $ctr - $desc"
+ echo -n "$failbuf"
+ echo "$result" | col -b | while read -r line; do echo "# $line"; done
+ printf "#\n# exit code %s\n" "$exitcode"
+ fi
+}
+
+_check() {
+ local command=$2
+ local expected_exitcode=${3:-0}
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+
+ eval "$TIMEOUT" "$command" &> check_output.$$ &
+ bgpid=$!
+ pid=$(pgrep -f "${command%%[|;&>]*}" | tail -n1)
+ wait $bgpid
+ exitcode=$?
+ result=$(tr -d '\0' < check_output.$$)
+ rm -f check_output.$$
+
+ expected_output="${expected_output//\$pid/$pid}"
+ unexpected_output="${unexpected_output//\$pid/$pid}"
+ all_lines_pattern="${all_lines_pattern//\$pid/$pid}"
+
+ failbuf=''
+ fail=0
+
+ # Test if the results matches if requested
+ if [ -n "$expected_output" ] && ! grep -qe "$expected_output" <<< "$result"; then
+ failure "# Output match failed: \"$expected_output\""
+ fi
+
+ if [ -n "$unexpected_output" ] && grep -qe "$unexpected_output" <<< "$result"; then
+ failure "# Output non-match failed: \"$unexpected_output\""
+ fi
+
+ if [ -n "$all_lines_pattern" ] && grep -vqe "$all_lines_pattern" <<< "$result"; then
+ failure "# All-lines pattern failed: \"$all_lines_pattern\""
+ fi
+
+ if [ $exitcode -ne "$expected_exitcode" ]; then
+ failure "# Expected exit code $expected_exitcode"
+ fi
+}
+
+check() {
+ # Simple check: run the command with given arguments and test exit code.
+ # If TEST_COUNT is set, run the test. Otherwise, just count.
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ _check "$@"
+ report "$1"
+ fi
+}
+
+check_if_exists() {
+ # Conditional check that skips if a file or folder doesn't exist
+ local desc=$1
+ local command=$2
+ local file=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ if [ ! -e "$file" ]; then
+ echo "ok $ctr - $desc # SKIP file not found: $file"
+ else
+ _check "$desc" "$command" 0 "$expected_output" \
+ "$unexpected_output" "$all_lines_pattern"
+ report "$desc"
+ fi
+ fi
+}
+
+set_timeout() {
+ TIMEOUT="timeout -v -k 15s $1"
+}
+
+set_expected_timeout() {
+ TIMEOUT="timeout --preserve-status -k 15s $1"
+}
+
+unset_timeout() {
+ unset TIMEOUT
+}
+
+test_end() {
+ # If running without TEST_COUNT, tests are not actually run, just
+ # counted. In that case, re-run the test with the correct count.
+ [ -z "$TEST_COUNT" ] && TEST_COUNT=$ctr exec bash "$0" || true
+}
+
+# Avoid any environmental discrepancies
+export LC_ALL=C
+unset_timeout
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 06/12] verification/rvgen: Fix options shared among commands
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
After rvgen was refactored to use subparsers, the common options (-a and
-D) were left in the main parser. This meant that they needed to be
called /before/ the subcommand and using them without subcommand was
allowed. This is not the original intent.
rvgen -D "some description" container -n name
Define the options as parent in the subparsers to allow them to be used
from both subcommands together with other options.
rvgen container -n name -D "some description"
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/__main__.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index 3be7f85fe37b..5c923dc10d0f 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -18,14 +18,16 @@ if __name__ == '__main__':
import sys
parser = argparse.ArgumentParser(description='Generate kernel rv monitor')
- parser.add_argument("-D", "--description", dest="description", required=False)
- parser.add_argument("-a", "--auto_patch", dest="auto_patch",
+
+ parent_parser = argparse.ArgumentParser(add_help=False)
+ parent_parser.add_argument("-D", "--description", dest="description", required=False)
+ parent_parser.add_argument("-a", "--auto_patch", dest="auto_patch",
action="store_true", required=False,
help="Patch the kernel in place")
subparsers = parser.add_subparsers(dest="subcmd", required=True)
- monitor_parser = subparsers.add_parser("monitor")
+ monitor_parser = subparsers.add_parser("monitor", parents=[parent_parser])
monitor_parser.add_argument('-n', "--model_name", dest="model_name")
monitor_parser.add_argument("-p", "--parent", dest="parent",
required=False, help="Create a monitor nested to parent")
@@ -36,7 +38,7 @@ if __name__ == '__main__':
monitor_parser.add_argument('-t', "--monitor_type", dest="monitor_type", required=True,
help=f"Available options: {', '.join(Monitor.monitor_types.keys())}")
- container_parser = subparsers.add_parser("container")
+ container_parser = subparsers.add_parser("container", parents=[parent_parser])
container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
params = parser.parse_args()
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 07/12] verification/rvgen: Add golden and spec folders for tests
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Create reference models specifications and generated files in the golded
folder. Those can be used as reference to validate rvgen still generates
files as expected in automated tests.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../rvgen/tests/golden/da_global/Kconfig | 9 +
.../rvgen/tests/golden/da_global/da_global.c | 95 +++++++
.../rvgen/tests/golden/da_global/da_global.h | 47 ++++
.../tests/golden/da_global/da_global_trace.h | 15 ++
.../tests/golden/da_perobj_parent/Kconfig | 11 +
.../da_perobj_parent/da_perobj_parent.c | 110 ++++++++
.../da_perobj_parent/da_perobj_parent.h | 64 +++++
.../da_perobj_parent/da_perobj_parent_trace.h | 15 ++
.../tests/golden/da_pertask_desc/Kconfig | 9 +
.../golden/da_pertask_desc/da_pertask_desc.c | 105 ++++++++
.../golden/da_pertask_desc/da_pertask_desc.h | 64 +++++
.../da_pertask_desc/da_pertask_desc_trace.h | 15 ++
.../rvgen/tests/golden/ha_percpu/Kconfig | 9 +
.../rvgen/tests/golden/ha_percpu/ha_percpu.c | 244 +++++++++++++++++
.../rvgen/tests/golden/ha_percpu/ha_percpu.h | 72 +++++
.../tests/golden/ha_percpu/ha_percpu_trace.h | 19 ++
.../rvgen/tests/golden/ltl_pertask/Kconfig | 9 +
.../tests/golden/ltl_pertask/ltl_pertask.c | 107 ++++++++
.../tests/golden/ltl_pertask/ltl_pertask.h | 108 ++++++++
.../golden/ltl_pertask/ltl_pertask_trace.h | 14 +
.../rvgen/tests/golden/test_container/Kconfig | 5 +
.../golden/test_container/test_container.c | 35 +++
.../golden/test_container/test_container.h | 3 +
.../rvgen/tests/golden/test_da/Kconfig | 9 +
.../rvgen/tests/golden/test_da/test_da.c | 95 +++++++
.../rvgen/tests/golden/test_da/test_da.h | 47 ++++
.../tests/golden/test_da/test_da_trace.h | 15 ++
.../rvgen/tests/golden/test_ha/Kconfig | 9 +
.../rvgen/tests/golden/test_ha/test_ha.c | 247 ++++++++++++++++++
.../rvgen/tests/golden/test_ha/test_ha.h | 72 +++++
.../tests/golden/test_ha/test_ha_trace.h | 19 ++
.../rvgen/tests/golden/test_ltl/Kconfig | 11 +
.../rvgen/tests/golden/test_ltl/test_ltl.c | 108 ++++++++
.../rvgen/tests/golden/test_ltl/test_ltl.h | 108 ++++++++
.../tests/golden/test_ltl/test_ltl_trace.h | 14 +
.../rvgen/tests/specs/test_da.dot | 16 ++
.../rvgen/tests/specs/test_da2.dot | 18 ++
.../rvgen/tests/specs/test_ha.dot | 27 ++
.../rvgen/tests/specs/test_invalid.dot | 8 +
.../rvgen/tests/specs/test_invalid.ltl | 1 +
.../rvgen/tests/specs/test_invalid_ha.dot | 16 ++
.../rvgen/tests/specs/test_ltl.ltl | 1 +
42 files changed, 2025 insertions(+)
create mode 100644 tools/verification/rvgen/tests/golden/da_global/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.c
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.h
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_container/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.c
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
create mode 100644 tools/verification/rvgen/tests/specs/test_da.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_da2.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.ltl
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ltl.ltl
diff --git a/tools/verification/rvgen/tests/golden/da_global/Kconfig b/tools/verification/rvgen/tests/golden/da_global/Kconfig
new file mode 100644
index 000000000000..799fbf11c3ac
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_GLOBAL
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "da_global monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.c b/tools/verification/rvgen/tests/golden/da_global/da_global.c
new file mode 100644
index 000000000000..ad4b939d2323
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_global"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_GLOBAL
+#include "da_global.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_da_global);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_da_global);
+}
+
+static int enable_da_global(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_da_global(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_global",
+ .description = "auto-generated",
+ .enable = enable_da_global,
+ .disable = disable_da_global,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_global(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_global(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_global);
+module_exit(unregister_da_global);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("da_global: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.h b/tools/verification/rvgen/tests/golden/da_global/da_global.h
new file mode 100644
index 000000000000..40b1f1c0c681
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_global automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_global
+
+enum states_da_global {
+ state_a_da_global,
+ state_b_da_global,
+ state_max_da_global,
+};
+
+#define INVALID_STATE state_max_da_global
+
+enum events_da_global {
+ event_1_da_global,
+ event_2_da_global,
+ event_max_da_global,
+};
+
+struct automaton_da_global {
+ char *state_names[state_max_da_global];
+ char *event_names[event_max_da_global];
+ unsigned char function[state_max_da_global][event_max_da_global];
+ unsigned char initial_state;
+ bool final_states[state_max_da_global];
+};
+
+static const struct automaton_da_global automaton_da_global = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_da_global, state_a_da_global },
+ { INVALID_STATE, state_a_da_global },
+ },
+ .initial_state = state_a_da_global,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
new file mode 100644
index 000000000000..4d2730b71dd0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_GLOBAL
+DEFINE_EVENT(event_da_monitor, event_da_global,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_da_global,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_DA_GLOBAL */
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
new file mode 100644
index 000000000000..249ba3aee8d7
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PEROBJ_PARENT
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_PARENT_MON
+ default y
+ select DA_MON_EVENTS_ID
+ bool "da_perobj_parent monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
new file mode 100644
index 000000000000..66f3a010876a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_perobj_parent"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/parent_mon/parent_mon.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_OBJ
+typedef /* XXX: define the target type */ *monitor_target;
+#include "da_perobj_parent.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_start_run_event(id, t, event_1_da_perobj_parent);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_2_da_perobj_parent);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_3_da_perobj_parent);
+}
+
+static int enable_da_perobj_parent(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+
+ return 0;
+}
+
+static void disable_da_perobj_parent(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_perobj_parent",
+ .description = "auto-generated",
+ .enable = enable_da_perobj_parent,
+ .disable = disable_da_perobj_parent,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_perobj_parent(void)
+{
+ return rv_register_monitor(&rv_this, &rv_parent_mon);
+}
+
+static void __exit unregister_da_perobj_parent(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_perobj_parent);
+module_exit(unregister_da_perobj_parent);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("da_perobj_parent: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
new file mode 100644
index 000000000000..3c8dc3b22443
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_perobj_parent automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_perobj_parent
+
+enum states_da_perobj_parent {
+ state_a_da_perobj_parent,
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ state_max_da_perobj_parent,
+};
+
+#define INVALID_STATE state_max_da_perobj_parent
+
+enum events_da_perobj_parent {
+ event_1_da_perobj_parent,
+ event_2_da_perobj_parent,
+ event_3_da_perobj_parent,
+ event_max_da_perobj_parent,
+};
+
+struct automaton_da_perobj_parent {
+ char *state_names[state_max_da_perobj_parent];
+ char *event_names[event_max_da_perobj_parent];
+ unsigned char function[state_max_da_perobj_parent][event_max_da_perobj_parent];
+ unsigned char initial_state;
+ bool final_states[state_max_da_perobj_parent];
+};
+
+static const struct automaton_da_perobj_parent automaton_da_perobj_parent = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_perobj_parent,
+ state_c_da_perobj_parent,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_perobj_parent,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
new file mode 100644
index 000000000000..59bfca8f73d2
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PEROBJ_PARENT
+DEFINE_EVENT(event_da_monitor_id, event_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PEROBJ_PARENT */
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
new file mode 100644
index 000000000000..c6f350179098
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PERTASK_DESC
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_ID
+ bool "da_pertask_desc monitor"
+ help
+ Custom description for testing
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
new file mode 100644
index 000000000000..bd76ecc3a998
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_pertask_desc"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+#include "da_pertask_desc.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_run_event(p, event_1_da_pertask_desc);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_2_da_pertask_desc);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_3_da_pertask_desc);
+}
+
+static int enable_da_pertask_desc(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ return 0;
+}
+
+static void disable_da_pertask_desc(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_pertask_desc",
+ .description = "Custom description for testing",
+ .enable = enable_da_pertask_desc,
+ .disable = disable_da_pertask_desc,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_pertask_desc(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_pertask_desc(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_pertask_desc);
+module_exit(unregister_da_pertask_desc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("da_pertask_desc: Custom description for testing");
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
new file mode 100644
index 000000000000..837b238754b0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_pertask_desc automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_pertask_desc
+
+enum states_da_pertask_desc {
+ state_a_da_pertask_desc,
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ state_max_da_pertask_desc,
+};
+
+#define INVALID_STATE state_max_da_pertask_desc
+
+enum events_da_pertask_desc {
+ event_1_da_pertask_desc,
+ event_2_da_pertask_desc,
+ event_3_da_pertask_desc,
+ event_max_da_pertask_desc,
+};
+
+struct automaton_da_pertask_desc {
+ char *state_names[state_max_da_pertask_desc];
+ char *event_names[event_max_da_pertask_desc];
+ unsigned char function[state_max_da_pertask_desc][event_max_da_pertask_desc];
+ unsigned char initial_state;
+ bool final_states[state_max_da_pertask_desc];
+};
+
+static const struct automaton_da_pertask_desc automaton_da_pertask_desc = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_pertask_desc,
+ state_c_da_pertask_desc,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_pertask_desc,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
new file mode 100644
index 000000000000..4e6086c4d86e
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PERTASK_DESC
+DEFINE_EVENT(event_da_monitor_id, event_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PERTASK_DESC */
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
new file mode 100644
index 000000000000..0cc185ccfddf
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_HA_PERCPU
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_IMPLICIT
+ bool "ha_percpu monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
new file mode 100644
index 000000000000..ba7a02a18f81
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ha_percpu"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "ha_percpu.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_ha_percpu)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_ha_percpu)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * 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).
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S2_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns);
+ return true;
+}
+
+static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == next_state)
+ return;
+ if (curr_state == S2_ha_percpu)
+ ha_inv_to_guard(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns);
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S0_ha_percpu && event == event1_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event2_ha_percpu) {
+ res = ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ } else if (curr_state == S2_ha_percpu && event == event1_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ ha_get_env(ha_mon, clk_ha_percpu, time_ns) < foo_ns;
+ else if (curr_state == S3_ha_percpu && event == event0_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_ha_percpu, time_ns) == 0ull);
+ else if (curr_state == S3_ha_percpu && event == event1_ha_percpu) {
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < foo_ns &&
+ ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_ha_percpu)
+ return;
+ if (next_state == S0_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_ha_percpu)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_ha_percpu)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event0_ha_percpu);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event1_ha_percpu);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event2_ha_percpu);
+}
+
+static int enable_ha_percpu(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_ha_percpu(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "ha_percpu",
+ .description = "auto-generated",
+ .enable = enable_ha_percpu,
+ .disable = disable_ha_percpu,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_ha_percpu(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_ha_percpu(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_ha_percpu);
+module_exit(unregister_ha_percpu);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("ha_percpu: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
new file mode 100644
index 000000000000..2538db4f6a26
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of ha_percpu automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME ha_percpu
+
+enum states_ha_percpu {
+ S0_ha_percpu,
+ S1_ha_percpu,
+ S2_ha_percpu,
+ S3_ha_percpu,
+ state_max_ha_percpu,
+};
+
+#define INVALID_STATE state_max_ha_percpu
+
+enum events_ha_percpu {
+ event0_ha_percpu,
+ event1_ha_percpu,
+ event2_ha_percpu,
+ event_max_ha_percpu,
+};
+
+enum envs_ha_percpu {
+ clk_ha_percpu,
+ env1_ha_percpu,
+ env2_ha_percpu,
+ env_max_ha_percpu,
+ env_max_stored_ha_percpu = env1_ha_percpu,
+};
+
+_Static_assert(env_max_stored_ha_percpu <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_ha_percpu {
+ char *state_names[state_max_ha_percpu];
+ char *event_names[event_max_ha_percpu];
+ char *env_names[env_max_ha_percpu];
+ unsigned char function[state_max_ha_percpu][event_max_ha_percpu];
+ unsigned char initial_state;
+ bool final_states[state_max_ha_percpu];
+};
+
+static const struct automaton_ha_percpu automaton_ha_percpu = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ { S0_ha_percpu, INVALID_STATE, S2_ha_percpu },
+ { INVALID_STATE, S2_ha_percpu, S3_ha_percpu },
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ },
+ .initial_state = S0_ha_percpu,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
new file mode 100644
index 000000000000..074ddff6a60d
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_HA_PERCPU
+DEFINE_EVENT(event_da_monitor, event_ha_percpu,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_ha_percpu,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+
+DEFINE_EVENT(error_env_da_monitor, error_env_ha_percpu,
+ TP_PROTO(char *state, char *event, char *env),
+ TP_ARGS(state, event, env));
+#endif /* CONFIG_RV_MON_HA_PERCPU */
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
new file mode 100644
index 000000000000..b37f46670bfd
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_LTL_PERTASK
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "ltl_pertask monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
new file mode 100644
index 000000000000..1b6897200e4b
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ltl_pertask"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "ltl_pertask.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_ltl_pertask(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_ltl_pertask(void)
+{
+ rv_detach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_sample_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_ltl_pertask = {
+ .name = "ltl_pertask",
+ .description = "auto-generated",
+ .enable = enable_ltl_pertask,
+ .disable = disable_ltl_pertask,
+};
+
+static int __init register_ltl_pertask(void)
+{
+ return rv_register_monitor(&rv_ltl_pertask, NULL);
+}
+
+static void __exit unregister_ltl_pertask(void)
+{
+ rv_unregister_monitor(&rv_ltl_pertask);
+}
+
+module_init(register_ltl_pertask);
+module_exit(unregister_ltl_pertask);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(/* TODO */);
+MODULE_DESCRIPTION("ltl_pertask: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
new file mode 100644
index 000000000000..e009b04174db
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME ltl_pertask
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (True)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (True)
+ __set_bit(S1, next);
+ if (True && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (True)
+ __set_bit(S1, next);
+ if (True && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
new file mode 100644
index 000000000000..ebd53621a5b1
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_LTL_PERTASK
+DEFINE_EVENT(event_ltl_monitor_id, event_ltl_pertask,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_ltl_pertask,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_LTL_PERTASK */
diff --git a/tools/verification/rvgen/tests/golden/test_container/Kconfig b/tools/verification/rvgen/tests/golden/test_container/Kconfig
new file mode 100644
index 000000000000..2becb65dddad
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/Kconfig
@@ -0,0 +1,5 @@
+config RV_MON_TEST_CONTAINER
+ depends on RV
+ bool "test_container monitor"
+ help
+ Test container for grouping monitors
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.c b/tools/verification/rvgen/tests/golden/test_container/test_container.c
new file mode 100644
index 000000000000..984e2eac7196
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+
+#define MODULE_NAME "test_container"
+
+#include "test_container.h"
+
+struct rv_monitor rv_test_container = {
+ .name = "test_container",
+ .description = "Test container for grouping monitors",
+ .enable = NULL,
+ .disable = NULL,
+ .reset = NULL,
+ .enabled = 0,
+};
+
+static int __init register_test_container(void)
+{
+ return rv_register_monitor(&rv_test_container, NULL);
+}
+
+static void __exit unregister_test_container(void)
+{
+ rv_unregister_monitor(&rv_test_container);
+}
+
+module_init(register_test_container);
+module_exit(unregister_test_container);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("test_container: Test container for grouping monitors");
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.h b/tools/verification/rvgen/tests/golden/test_container/test_container.h
new file mode 100644
index 000000000000..83e434432650
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern struct rv_monitor rv_test_container;
diff --git a/tools/verification/rvgen/tests/golden/test_da/Kconfig b/tools/verification/rvgen/tests/golden/test_da/Kconfig
new file mode 100644
index 000000000000..0143a148ef34
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_DA
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "test_da monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.c b/tools/verification/rvgen/tests/golden/test_da/test_da.c
new file mode 100644
index 000000000000..b63bbf4e35c5
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_da"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "test_da.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_test_da);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_test_da);
+}
+
+static int enable_test_da(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_test_da(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_da",
+ .description = "auto-generated",
+ .enable = enable_test_da,
+ .disable = disable_test_da,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_da(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_da(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_da);
+module_exit(unregister_test_da);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("test_da: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.h b/tools/verification/rvgen/tests/golden/test_da/test_da.h
new file mode 100644
index 000000000000..d55795efbb61
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_da automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_da
+
+enum states_test_da {
+ state_a_test_da,
+ state_b_test_da,
+ state_max_test_da,
+};
+
+#define INVALID_STATE state_max_test_da
+
+enum events_test_da {
+ event_1_test_da,
+ event_2_test_da,
+ event_max_test_da,
+};
+
+struct automaton_test_da {
+ char *state_names[state_max_test_da];
+ char *event_names[event_max_test_da];
+ unsigned char function[state_max_test_da][event_max_test_da];
+ unsigned char initial_state;
+ bool final_states[state_max_test_da];
+};
+
+static const struct automaton_test_da automaton_test_da = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_test_da, state_a_test_da },
+ { INVALID_STATE, state_a_test_da },
+ },
+ .initial_state = state_a_test_da,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
new file mode 100644
index 000000000000..8bd67115d244
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_DA
+DEFINE_EVENT(event_da_monitor, event_test_da,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_test_da,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_TEST_DA */
diff --git a/tools/verification/rvgen/tests/golden/test_ha/Kconfig b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
new file mode 100644
index 000000000000..f4048290c774
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_HA
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "test_ha monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.c b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
new file mode 100644
index 000000000000..485fcd0259b6
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ha"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "test_ha.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_test_ha)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_test_ha)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * 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).
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S2_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns);
+ return true;
+}
+
+static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == next_state)
+ return;
+ if (curr_state == S2_test_ha)
+ ha_inv_to_guard(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns);
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S0_test_ha && event == event1_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event2_test_ha) {
+ res = ha_get_env(ha_mon, env1_test_ha, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ } else if (curr_state == S2_test_ha && event == event1_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ ha_get_env(ha_mon, clk_test_ha, time_ns) < foo_ns;
+ else if (curr_state == S3_test_ha && event == event0_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_test_ha, time_ns) == 0ull);
+ else if (curr_state == S3_test_ha && event == event1_test_ha) {
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < foo_ns &&
+ ha_get_env(ha_mon, env1_test_ha, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_test_ha)
+ return;
+ if (next_state == S0_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_test_ha)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_test_ha)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_event(p, event0_test_ha);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event1_test_ha);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event2_test_ha);
+}
+
+static int enable_test_ha(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_test_ha(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ha",
+ .description = "auto-generated",
+ .enable = enable_test_ha,
+ .disable = disable_test_ha,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_ha(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ha(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ha);
+module_exit(unregister_test_ha);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_DESCRIPTION("test_ha: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
new file mode 100644
index 000000000000..949fa4453403
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_ha automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_ha
+
+enum states_test_ha {
+ S0_test_ha,
+ S1_test_ha,
+ S2_test_ha,
+ S3_test_ha,
+ state_max_test_ha,
+};
+
+#define INVALID_STATE state_max_test_ha
+
+enum events_test_ha {
+ event0_test_ha,
+ event1_test_ha,
+ event2_test_ha,
+ event_max_test_ha,
+};
+
+enum envs_test_ha {
+ clk_test_ha,
+ env1_test_ha,
+ env2_test_ha,
+ env_max_test_ha,
+ env_max_stored_test_ha = env1_test_ha,
+};
+
+_Static_assert(env_max_stored_test_ha <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_test_ha {
+ char *state_names[state_max_test_ha];
+ char *event_names[event_max_test_ha];
+ char *env_names[env_max_test_ha];
+ unsigned char function[state_max_test_ha][event_max_test_ha];
+ unsigned char initial_state;
+ bool final_states[state_max_test_ha];
+};
+
+static const struct automaton_test_ha automaton_test_ha = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ { S0_test_ha, INVALID_STATE, S2_test_ha },
+ { INVALID_STATE, S2_test_ha, S3_test_ha },
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ },
+ .initial_state = S0_test_ha,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
new file mode 100644
index 000000000000..381bafcb3322
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_HA
+DEFINE_EVENT(event_da_monitor_id, event_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_test_ha,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *env),
+ TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TEST_HA */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
new file mode 100644
index 000000000000..e2d0e721f180
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_LTL
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_LTL_PARENT
+ default y
+ select LTL_MON_EVENTS_ID
+ bool "test_ltl monitor"
+ help
+ Simple description
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
new file mode 100644
index 000000000000..92c69b9d9a41
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ltl"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/ltl_parent/ltl_parent.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_ltl.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_ltl(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_ltl(void)
+{
+ rv_detach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_sample_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_test_ltl = {
+ .name = "test_ltl",
+ .description = "Simple description",
+ .enable = enable_test_ltl,
+ .disable = disable_test_ltl,
+};
+
+static int __init register_test_ltl(void)
+{
+ return rv_register_monitor(&rv_test_ltl, &rv_ltl_parent);
+}
+
+static void __exit unregister_test_ltl(void)
+{
+ rv_unregister_monitor(&rv_test_ltl);
+}
+
+module_init(register_test_ltl);
+module_exit(unregister_test_ltl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(/* TODO */);
+MODULE_DESCRIPTION("test_ltl: Simple description");
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
new file mode 100644
index 000000000000..8484f8224a2b
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_ltl
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (True)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (True)
+ __set_bit(S1, next);
+ if (True && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (True)
+ __set_bit(S1, next);
+ if (True && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (True)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
new file mode 100644
index 000000000000..3571b004c114
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_LTL
+DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_LTL */
diff --git a/tools/verification/rvgen/tests/specs/test_da.dot b/tools/verification/rvgen/tests/specs/test_da.dot
new file mode 100644
index 000000000000..e555c239b221
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_a" [ label = "event_2" ];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_da2.dot b/tools/verification/rvgen/tests/specs/test_da2.dot
new file mode 100644
index 000000000000..bfee6e535cf7
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da2.dot
@@ -0,0 +1,18 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = circle] "state_c"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_a" -> "state_c" [ label = "event_2" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ "state_b" -> "state_c" [ label = "event_3" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ha.dot b/tools/verification/rvgen/tests/specs/test_ha.dot
new file mode 100644
index 000000000000..786aa8b22098
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ha.dot
@@ -0,0 +1,27 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = circle] "S1"};
+ {node [shape = plaintext, style=invis, label=""] "__init_S0"};
+ {node [shape = doublecircle] "S0"};
+ {node [shape = circle] "S0"};
+ {node [shape = circle] "S2"};
+ {node [shape = circle] "S3"};
+ "__init_S0" -> "S0";
+ "S0" [label = "S0\nclk < bar_ns()", color = green3];
+ "S1" [label = "S1"];
+ "S2" [label = "S2\nclk < BAR_NS()"];
+ "S3" [label = "S3"];
+ "S1" -> "S0" [ label = "event0;reset(clk)" ];
+ "S0" -> "S1" [ label = "event1;reset(clk)" ];
+ "S0" -> "S0" [ label = "event0;reset(clk)" ];
+ "S1" -> "S2" [ label = "event2;env1 == 0;reset(clk)" ];
+ "S2" -> "S3" [ label = "event2" ];
+ "S2" -> "S2" [ label = "event1;clk < foo_ns" ];
+ "S3" -> "S0" [ label = "event0;clk < FOO_NS && env2 == 0" ];
+ "S3" -> "S1" [ label = "event1;clk < foo_ns && env1 == 1;reset(clk)" ];
+ { rank = min ;
+ "__init_S0";
+ "S0";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.dot b/tools/verification/rvgen/tests/specs/test_invalid.dot
new file mode 100644
index 000000000000..17c63fc57f17
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.dot
@@ -0,0 +1,8 @@
+digraph invalid {
+ {node [shape = circle] "init"};
+ {node [shape = circle] "state1"};
+ "init" [label = "init"];
+ "init" -> "state1" [ label = "event_a" ];
+ "state1" [label = "state1"];
+ "state1" -> "init" [ label = "event_b" ];
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.ltl b/tools/verification/rvgen/tests/specs/test_invalid.ltl
new file mode 100644
index 000000000000..cf36307e003c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.ltl
@@ -0,0 +1 @@
+RULE = A invalid B
diff --git a/tools/verification/rvgen/tests/specs/test_invalid_ha.dot b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
new file mode 100644
index 000000000000..06de6aa8709f
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a;clk < 1"];
+ "state_a" -> "state_a" [ label = "event_2;reset(clk)" ];
+ "state_a" -> "state_b" [ label = "event_1;wrong_constraint" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ltl.ltl b/tools/verification/rvgen/tests/specs/test_ltl.ltl
new file mode 100644
index 000000000000..5ed658abd69c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ltl.ltl
@@ -0,0 +1 @@
+RULE = always (EVENT_A imply eventually EVENT_B)
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 08/12] verification/rvgen: Add selftests
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
The rvgen code generator needs validation to ensure it produces correct
monitor implementations from input specifications.
Add selftests with golden reference outputs covering all monitor classes
(DA, HA, LTL) and types (global, per_cpu, per_task, per_obj), including
optional features like descriptions and parent monitors. Container
generation and error handling (missing files, invalid specifications,
missing arguments) are also validated against expected output.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/Makefile | 4 +
.../rvgen/tests/rvgen_container.t | 20 +++++
.../verification/rvgen/tests/rvgen_monitor.t | 87 +++++++++++++++++++
tools/verification/tests/engine.sh | 32 +++++++
4 files changed, 143 insertions(+)
create mode 100644 tools/verification/rvgen/tests/rvgen_container.t
create mode 100644 tools/verification/rvgen/tests/rvgen_monitor.t
diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile
index cfc4056c1e87..2a2b9e64ea42 100644
--- a/tools/verification/rvgen/Makefile
+++ b/tools/verification/rvgen/Makefile
@@ -13,6 +13,10 @@ all:
.PHONY: clean
clean:
+.PHONY: check
+check:
+ prove -o --directives -f tests/
+
.PHONY: install
install:
$(INSTALL) rvgen/automata.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/automata.py
diff --git a/tools/verification/rvgen/tests/rvgen_container.t b/tools/verification/rvgen/tests/rvgen_container.t
new file mode 100644
index 000000000000..fa4fb3db8288
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_container.t
@@ -0,0 +1,20 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help tests
+check "verify container subcommand help" \
+ "$RVGEN container -h" 0 "model_name" "class"
+
+check_and_compare_folder "container with description" \
+ "$RVGEN container -n test_container -D 'Test container for grouping monitors'" \
+ "test_container" "Writing the monitor into the directory test_container"
+
+# Error handling tests
+check "missing required model_name" \
+ "$RVGEN container" 2 "the following arguments are required: -n/--model_name"
+
+test_end
diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t
new file mode 100644
index 000000000000..261476504eee
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_monitor.t
@@ -0,0 +1,87 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help and basic tests
+check "verify help page" \
+ "$RVGEN --help" 0 "Generate kernel rv monitor"
+
+check "verify monitor subcommand help" \
+ "$RVGEN monitor --help" 0 "Monitor class"
+
+# DA monitor tests - test all monitor types
+check_and_compare_folder "DA per_cpu (default name)" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu" \
+ "test_da" "obj-\$(CONFIG_RV_MON_TEST_DA) += monitors/test_da/test_da.o"
+
+check_and_compare_folder "DA global type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t global -n da_global" \
+ "da_global" "DA_MON_EVENTS_IMPLICIT"
+
+check_and_compare_folder "DA per_task with description" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_task -n da_pertask_desc -D 'Custom description for testing'" \
+ "da_pertask_desc" "#include <monitors/da_pertask_desc/da_pertask_desc_trace.h>"
+
+check_and_compare_folder "DA per_obj with parent" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_obj -n da_perobj_parent -p parent_mon" \
+ "da_perobj_parent" "DA_MON_EVENTS_ID"
+
+# HA monitor tests
+check_and_compare_folder "HA per_task (default name)" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task" \
+ "test_ha" "HA_MON_EVENTS_ID"
+
+check_and_compare_folder "HA per_cpu type" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_cpu -n ha_percpu" \
+ "ha_percpu" "HA_MON_EVENTS_IMPLICIT"
+
+# LTL monitor test
+check_and_compare_folder "LTL per_task" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n ltl_pertask" \
+ "ltl_pertask" "source \"kernel/trace/rv/monitors/ltl_pertask/Kconfig\""
+
+check_and_compare_folder "LTL per_task with parent and description (default name)" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -p ltl_parent -D 'Simple description'" \
+ "test_ltl" "LTL_MON_EVENTS_ID"
+
+# Error handling tests
+check "missing required spec argument" \
+ "$RVGEN monitor -c da -t per_cpu" 2 \
+ "the following arguments are required: -s/--spec" "Traceback (most recent call last)"
+
+check "missing required monitor type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot" 2 \
+ "the following arguments are required: -t/--monitor_type" "Traceback (most recent call last)"
+
+check "missing required monitor class" \
+ "$RVGEN monitor -s tests/specs/test_da.dot -t per_cpu" 2 \
+ "the following arguments are required: -c/--class" "Traceback (most recent call last)"
+
+check "invalid monitor class" \
+ "$RVGEN monitor -c invalid -s tests/specs/test_da.dot -t per_cpu" 1 \
+ "Unknown monitor class" "Traceback (most recent call last)"
+
+check "missing dot file" \
+ "$RVGEN monitor -c da -s tests/specs/nonexistent.dot -t per_cpu" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "missing ltl file" \
+ "$RVGEN monitor -c ltl -s tests/specs/nonexistent.ltl -t per_task" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "invalid dot file syntax" \
+ "$RVGEN monitor -c da -s tests/specs/test_invalid.dot -t per_cpu" 1 \
+ "Not a valid .dot format" "Traceback (most recent call last)"
+
+check "invalid ha file syntax" \
+ "$RVGEN monitor -c ha -s tests/specs/test_invalid_ha.dot -t per_obj" 1 \
+ "Unrecognised event constraint" "Traceback (most recent call last)"
+
+check "invalid ltl file syntax" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_invalid.ltl -t per_task" 1 \
+ "Illegal character 'i'" "Traceback (most recent call last)"
+
+test_end
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
index cd367dabef53..ef89df9112a4 100644
--- a/tools/verification/tests/engine.sh
+++ b/tools/verification/tests/engine.sh
@@ -5,6 +5,8 @@ test_begin() {
# included correctly.
ctr=0
[ -z "$RV" ] && RV="../rv/rv"
+ [ -z "$RVGEN" ] && RVGEN="python ../rvgen"
+ [ -z "$GOLDEN_DIR" ] && GOLDEN_DIR="tests/golden"
[ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
}
@@ -101,6 +103,36 @@ check_if_exists() {
fi
}
+check_and_compare_folder() {
+ # Run command, compare generated folder to golden, and cleanup
+ local desc=$1
+ local command=$2
+ local generated_dir=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local golden_dir="$GOLDEN_DIR/$generated_dir"
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ _check "$desc" "$command" 0 "$expected_output" "$unexpected_output"
+
+ if [ "$fail" -eq 0 ] && [ ! -d "$generated_dir" ]; then
+ failure "# Generated directory not found: $generated_dir"
+ fi
+
+ if [ "$fail" -ne 0 ]; then
+ :
+ elif ! diff -r "$generated_dir" "$golden_dir" &> /dev/null; then
+ failure "# Directories differ:"
+ failbuf+=$(diff -r "$generated_dir" "$golden_dir" 2>&1 | sed 's/^/# /')
+ fi
+
+ report "$1"
+
+ rm -rf "$generated_dir"
+ fi
+}
+
set_timeout() {
TIMEOUT="timeout -v -k 15s $1"
}
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 09/12] rv: Add KUnit stub to rv_react() and rv_*_task_monitor_slot()
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Add KUNIT_STATIC_STUB_REDIRECT to allow those functions to be stubbed in
a KUnit test. This is useful to catch reaction without creating a custom
reactor and going through the effort of setting it from a test.
rv_{get/put}_task_monitor_slot() rely on a lock, but this isn't
necessary during a unit test, so simply skip the calls.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
kernel/trace/rv/rv.c | 5 +++++
kernel/trace/rv/rv_reactors.c | 3 +++
2 files changed, 8 insertions(+)
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index ee4e68102f17..f59385a24fa1 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -142,6 +142,7 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <kunit/static_stub.h>
#ifdef CONFIG_RV_MON_EVENTS
#define CREATE_TRACE_POINTS
@@ -171,6 +172,8 @@ int rv_get_task_monitor_slot(void)
{
int i;
+ KUNIT_STATIC_STUB_REDIRECT(rv_get_task_monitor_slot);
+
lockdep_assert_held(&rv_interface_lock);
if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
@@ -192,6 +195,8 @@ int rv_get_task_monitor_slot(void)
void rv_put_task_monitor_slot(int slot)
{
+ KUNIT_STATIC_STUB_REDIRECT(rv_put_task_monitor_slot, slot);
+
lockdep_assert_held(&rv_interface_lock);
if (slot < 0 || slot >= CONFIG_RV_PER_TASK_MONITORS) {
diff --git a/kernel/trace/rv/rv_reactors.c b/kernel/trace/rv/rv_reactors.c
index 460af07f7aba..f9b6a034c022 100644
--- a/kernel/trace/rv/rv_reactors.c
+++ b/kernel/trace/rv/rv_reactors.c
@@ -63,6 +63,7 @@
#include <linux/lockdep.h>
#include <linux/slab.h>
+#include <kunit/static_stub.h>
#include "rv.h"
@@ -468,6 +469,8 @@ void rv_react(struct rv_monitor *monitor, const char *msg, ...)
static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE);
va_list args;
+ KUNIT_STATIC_STUB_REDIRECT(rv_react, monitor, msg);
+
if (!rv_reacting_on() || !monitor->react)
return;
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 10/12] rv: Add KUnit tests for some DA/HA monitors
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Validate the functionality of DA monitors by injecting events in a
controlled environment (KUnit) and expecting reactions.
Events handlers are called directly from the monitor source files
without using system events and with dummy arguments (e.g. no real
tasks). If the provided sequence of events incurs a violation, the test
expects the stub version of rv_react() to be called.
This testing method can validate the entire monitor implementation since
it sits between the monitor and the system (in place of the
tracepoints). All sorts of system and timing events can be emulated
without affecting the running kernel.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 31 +++++++++++
kernel/trace/rv/Kconfig | 11 ++++
kernel/trace/rv/Makefile | 3 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 30 ++++++++++
kernel/trace/rv/monitors/opid/opid.c | 27 +++++++++
kernel/trace/rv/monitors/sco/sco.c | 23 ++++++++
kernel/trace/rv/monitors/sssw/sssw.c | 27 +++++++++
kernel/trace/rv/monitors/sts/sts.c | 35 ++++++++++++
kernel/trace/rv/rv_monitors_test.c | 70 ++++++++++++++++++++++++
kernel/trace/rv/rv_monitors_test.h | 69 +++++++++++++++++++++++
10 files changed, 326 insertions(+)
create mode 100644 kernel/trace/rv/rv_monitors_test.c
create mode 100644 kernel/trace/rv/rv_monitors_test.h
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 39765ff6f098..e85a82ad6c7b 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -817,4 +817,35 @@ static inline void da_reset(da_id_type id, monitor_target target)
}
#endif /* RV_MON_TYPE */
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include <kunit/test.h>
+
+/*
+ * rv_prepare_test - Disable the monitor for a kunit test
+ */
+static inline void da_teardown_test(void *arg)
+{
+ struct rv_monitor *rv_this = arg;
+
+ rv_this->enabled = 0;
+ da_monitor_destroy();
+}
+
+/*
+ * rv_prepare_test - Enable the monitor for a kunit test
+ *
+ * Do the bare minimum to set up the monitor, make sure it is not active and
+ * real tracepoint handlers are NOT attached.
+ */
+static inline void da_prepare_test(struct kunit *test, struct rv_monitor *rv_this)
+{
+ KUNIT_ASSERT_FALSE(test, rv_this->enabled);
+ da_monitor_init();
+ rv_this->enabled = 1;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ kunit_add_action_or_reset(test, da_teardown_test, rv_this));
+}
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+
#endif
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 3884b14df375..d7dba4453bd3 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -111,3 +111,14 @@ config RV_REACT_PANIC
help
Enables the panic reactor. The panic reactor emits a printk()
message if an exception is found and panic()s the system.
+
+config RV_MONITORS_KUNIT_TEST
+ bool "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS
+ depends on KUNIT=y && RV
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the RV (Runtime Verification) monitors.
+ These tests verify that monitors correctly detect violations by
+ triggering fake events and validating the expected reactions.
+
+ If unsure, say N.
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 94498da35b37..6fdf055a57c4 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -24,3 +24,6 @@ obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
+obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
+# Stubbing rv_react triggers the error
+CFLAGS_rv_monitors_test.o += -Wno-suggest-attribute=format
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 31f90f3638d8..57b8b7245552 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -291,3 +291,33 @@ module_exit(unregister_nomiss);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline.");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_nomiss(struct kunit *test)
+{
+ static struct task_struct *target, *other;
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ da_prepare_test(test, &rv_this);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ other = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ target->pid = 99;
+ target->policy = SCHED_DEADLINE;
+ target->dl.runtime = 10000;
+ target->dl.deadline = 20000;
+
+ handle_newtask(NULL, target, 0);
+
+ /* Task gets preempted and can't terminate before deadline */
+ handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ handle_dl_replenish(NULL, &target->dl, 0, DL_TASK);
+ udelay(10 / 1000);
+ handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ udelay(10 + deadline_thresh / 1000);
+ handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_nomiss);
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 4594c7c46601..cddd84deafdc 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -121,3 +121,30 @@ module_exit(unregister_opid);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("opid: operations with preemption and irq disabled.");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_opid(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ da_prepare_test(test, &rv_this);
+
+ /*
+ * The handlers are called with an additional level of preemption,
+ * ensure we start from 0 but apply it here to avoid warnings.
+ */
+ KUNIT_ASSERT_TRUE(test, preemptible());
+ guard(preempt)();
+
+ /* Wakeup with preemption and interrupts enabled */
+ handle_sched_waking(NULL, NULL);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+
+ /* Need resched with interrupts enabled */
+ scoped_guard(preempt)
+ handle_sched_need_resched(NULL, NULL, 0, TIF_NEED_RESCHED);
+}
+EXPORT_SYMBOL_GPL(rv_test_opid);
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c
index 5a3bd5e16e62..5f52400fa452 100644
--- a/kernel/trace/rv/monitors/sco/sco.c
+++ b/kernel/trace/rv/monitors/sco/sco.c
@@ -83,3 +83,26 @@ module_exit(unregister_sco);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sco: scheduling context operations.");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_sco(struct kunit *test)
+{
+ static struct task_struct *target;
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ da_prepare_test(test, &rv_this);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+
+ /* The handlers are called with preemption disabled. */
+ guard(preempt)();
+
+ /* Set state while scheduling */
+ handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ handle_schedule_entry(NULL, false);
+ handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_sco);
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c
index a91321c890cd..7660adf304f7 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.c
+++ b/kernel/trace/rv/monitors/sssw/sssw.c
@@ -112,3 +112,30 @@ module_exit(unregister_sssw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sssw: set state sleep and wakeup.");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_sssw(struct kunit *test)
+{
+ static struct task_struct *target, *other;
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ da_prepare_test(test, &rv_this);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ other = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+
+ /* Suspend without setting to sleepable */
+ handle_sched_set_state(NULL, target, TASK_RUNNING);
+ handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+
+ /* Switch in after suspension without wakeup */
+ handle_sched_wakeup(NULL, target);
+ handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+ handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_sssw);
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c
index ce031cbf202a..0cb7ab5bc51b 100644
--- a/kernel/trace/rv/monitors/sts/sts.c
+++ b/kernel/trace/rv/monitors/sts/sts.c
@@ -152,3 +152,38 @@ module_exit(unregister_sts);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sts: schedule implies task switch.");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_sts(struct kunit *test)
+{
+ static struct task_struct *target, *other;
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ da_prepare_test(test, &rv_this);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ other = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ /* Per-CPU monitor, make sure we don't change CPU mid-test */
+ guard(migrate)();
+
+ handle_schedule_exit(NULL, false);
+
+ /* Switch without disabling interrupts */
+ handle_schedule_exit(NULL, false);
+ handle_schedule_entry(NULL, false);
+ handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+
+ handle_schedule_exit(NULL, false);
+
+ /* Schedule from interrupt context */
+ handle_schedule_entry(NULL, false);
+ handle_irq_disable(NULL, 0, 0);
+ handle_irq_entry(NULL, 0, NULL);
+ handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ handle_irq_enable(NULL, 0, 0);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_sts);
+#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
new file mode 100644
index 000000000000..bffb960ecba7
--- /dev/null
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2028 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * RV debug trigger kunit tests:
+ * Tests the RV monitors by triggering fake events to verify monitor
+ * behavior and reactions. Tests start from the first defined event and
+ * trigger events in order to verify error detection.
+ */
+#include "rv_monitors_test.h"
+#include <kunit/static_stub.h>
+#include <kunit/test-bug.h>
+#include <linux/kernel.h>
+#include <linux/rv.h>
+
+__printf(2, 3)
+static void stub_rv_react(struct rv_monitor *monitor, const char *msg, ...)
+{
+ struct rv_kunit_ctx *ctx = kunit_get_current_test()->priv;
+
+ ++ctx->reactions;
+}
+
+static int stub_rv_get_task_monitor_slot(void)
+{
+ return 0;
+}
+
+static void stub_rv_put_task_monitor_slot(int slot)
+{
+}
+
+static int rv_trigger_test_init(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+ test->priv = ctx;
+
+ kunit_activate_static_stub(test, rv_react, stub_rv_react);
+ kunit_activate_static_stub(test, rv_get_task_monitor_slot,
+ stub_rv_get_task_monitor_slot);
+ kunit_activate_static_stub(test, rv_put_task_monitor_slot,
+ stub_rv_put_task_monitor_slot);
+
+ return 0;
+}
+
+static struct kunit_case rv_trigger_test_cases[] = {
+ KUNIT_CASE(rv_test_sco),
+ KUNIT_CASE(rv_test_sssw),
+ KUNIT_CASE(rv_test_sts),
+ KUNIT_CASE(rv_test_opid),
+ KUNIT_CASE(rv_test_nomiss),
+ {}
+};
+
+static struct kunit_suite rv_trigger_test_suite = {
+ .name = "rv_trigger",
+ .init = rv_trigger_test_init,
+ .test_cases = rv_trigger_test_cases,
+};
+
+kunit_test_suites(&rv_trigger_test_suite);
+
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("RV debug trigger kunit tests: test monitors by triggering reactions");
+MODULE_LICENSE("GPL");
diff --git a/kernel/trace/rv/rv_monitors_test.h b/kernel/trace/rv/rv_monitors_test.h
new file mode 100644
index 000000000000..968b07565a61
--- /dev/null
+++ b/kernel/trace/rv/rv_monitors_test.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <kunit/test.h>
+#include <linux/delay.h>
+
+struct rv_kunit_ctx {
+ int reactions, expected;
+ int cpu;
+ struct task_struct *curr;
+};
+
+#define RV_KUNIT_EXPECT_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ++ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#define RV_KUNIT_EXPECT_NO_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#ifdef CONFIG_RV_MON_SCO
+extern void rv_test_sco(struct kunit *test);
+#else
+static inline void rv_test_sco(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
+
+#ifdef CONFIG_RV_MON_SSSW
+extern void rv_test_sssw(struct kunit *test);
+#else
+static inline void rv_test_sssw(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
+
+#ifdef CONFIG_RV_MON_STS
+extern void rv_test_sts(struct kunit *test);
+#else
+static inline void rv_test_sts(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
+
+#ifdef CONFIG_RV_MON_OPID
+extern void rv_test_opid(struct kunit *test);
+#else
+static inline void rv_test_opid(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
+
+#ifdef CONFIG_RV_MON_NOMISS
+extern void rv_test_nomiss(struct kunit *test);
+#else
+static inline void rv_test_nomiss(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 11/12] rv: Add KUnit stubs for current and smp_processor_id()
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Some monitors do not only rely on tracepoint arguments but also on the
currently executing task and current processor.
This makes it more challenging to mock events in KUnit.
Define wrapper functions around current and smp_processor_id(), the
functionality is stubbed only during KUnit, however the additional
function call is necessary whenever the KUnit tests are built in.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 1 +
include/rv/kunit_stubs.h | 17 ++++++++++++
include/rv/ltl_monitor.h | 1 +
kernel/trace/rv/Kconfig | 3 +++
.../trace/rv/monitors/pagefault/pagefault.c | 2 +-
kernel/trace/rv/monitors/sleep/sleep.c | 22 +++++++--------
kernel/trace/rv/rv_monitors_test.c | 27 +++++++++++++++++++
kernel/trace/rv/rv_monitors_test.h | 3 +++
8 files changed, 64 insertions(+), 12 deletions(-)
create mode 100644 include/rv/kunit_stubs.h
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index e85a82ad6c7b..9c2a051608ff 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -15,6 +15,7 @@
#define _RV_DA_MONITOR_H
#include <rv/automata.h>
+#include <rv/kunit_stubs.h>
#include <linux/rv.h>
#include <linux/stringify.h>
#include <linux/bug.h>
diff --git a/include/rv/kunit_stubs.h b/include/rv/kunit_stubs.h
new file mode 100644
index 000000000000..ed04c56b440f
--- /dev/null
+++ b/include/rv/kunit_stubs.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * Declaration of wrappers to allow stubbing core functionality like current
+ * and smp_processor_id().
+ * Necessary only when mocking may be needed. If the RV KUnit test is
+ * enabled, these wrapper incur an additional function call overhead.
+ */
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+struct task_struct *rv_get_current(void);
+int rv_current_cpu(void);
+#else
+#define rv_get_current() current
+#define rv_current_cpu() smp_processor_id()
+#endif
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index eff60cd61106..0f2c3820b9b8 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -9,6 +9,7 @@
#include <linux/stringify.h>
#include <linux/seq_buf.h>
#include <rv/instrumentation.h>
+#include <rv/kunit_stubs.h>
#include <trace/events/task.h>
#include <trace/events/sched.h>
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index d7dba4453bd3..e76841a6ed35 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -121,4 +121,7 @@ config RV_MONITORS_KUNIT_TEST
These tests verify that monitors correctly detect violations by
triggering fake events and validating the expected reactions.
+ Enabling this may slighly increase overhead of some monitors even
+ when the KUnit test is not running.
+
If unsure, say N.
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 9fe6123b2200..56abe5079676 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -38,7 +38,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs,
unsigned long error_code)
{
- ltl_atom_pulse(current, LTL_PAGEFAULT, true);
+ ltl_atom_pulse(rv_get_current(), LTL_PAGEFAULT, true);
}
static int enable_pagefault(void)
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 8dfe5ec13e19..7c16967add70 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -102,7 +102,7 @@ static void handle_sched_waking(void *data, struct task_struct *task)
if (this_cpu_read(hardirq_context)) {
ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true);
} else if (in_task()) {
- if (current->prio <= task->prio)
+ if (rv_get_current()->prio <= task->prio)
ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true);
} else if (in_nmi()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true);
@@ -112,12 +112,12 @@ static void handle_sched_waking(void *data, struct task_struct *task)
static void handle_contention_begin(void *data, void *lock, unsigned int flags)
{
if (flags & LCB_F_RT)
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, true);
}
static void handle_contention_end(void *data, void *lock, int ret)
{
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, false);
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
@@ -126,7 +126,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
unsigned long args[6];
int op, cmd;
- mon = ltl_get_monitor(current);
+ mon = ltl_get_monitor(rv_get_current());
switch (id) {
#ifdef __NR_clock_nanosleep
@@ -135,11 +135,11 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_clock_nanosleep_time64
case __NR_clock_nanosleep_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, true);
break;
#ifdef __NR_futex
@@ -148,19 +148,19 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_futex_time64
case __NR_futex_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
op = args[1];
cmd = op & FUTEX_CMD_MASK;
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_LOCK_PI2:
- ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_LOCK_PI, true);
break;
case FUTEX_WAIT:
case FUTEX_WAIT_BITSET:
case FUTEX_WAIT_REQUEUE_PI:
- ltl_atom_update(current, LTL_FUTEX_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_WAIT, true);
break;
}
break;
@@ -174,7 +174,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
{
- struct ltl_monitor *mon = ltl_get_monitor(current);
+ struct ltl_monitor *mon = ltl_get_monitor(rv_get_current());
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
@@ -182,7 +182,7 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, false);
}
static void handle_kthread_stop(void *data, struct task_struct *task)
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index bffb960ecba7..a57a40932d34 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -8,6 +8,7 @@
* trigger events in order to verify error detection.
*/
#include "rv_monitors_test.h"
+#include <rv/kunit_stubs.h>
#include <kunit/static_stub.h>
#include <kunit/test-bug.h>
#include <linux/kernel.h>
@@ -30,6 +31,30 @@ static void stub_rv_put_task_monitor_slot(int slot)
{
}
+struct task_struct *rv_get_current(void)
+{
+ KUNIT_STATIC_STUB_REDIRECT(rv_get_current);
+ return current;
+}
+int rv_current_cpu(void)
+{
+ KUNIT_STATIC_STUB_REDIRECT(rv_current_cpu);
+ return smp_processor_id();
+}
+
+static struct task_struct *stub_rv_get_current(void)
+{
+ struct rv_kunit_ctx *ctx = kunit_get_current_test()->priv;
+
+ return ctx->curr;
+}
+static int stub_rv_current_cpu(void)
+{
+ struct rv_kunit_ctx *ctx = kunit_get_current_test()->priv;
+
+ return ctx->cpu;
+}
+
static int rv_trigger_test_init(struct kunit *test)
{
struct rv_kunit_ctx *ctx;
@@ -44,6 +69,8 @@ static int rv_trigger_test_init(struct kunit *test)
stub_rv_get_task_monitor_slot);
kunit_activate_static_stub(test, rv_put_task_monitor_slot,
stub_rv_put_task_monitor_slot);
+ kunit_activate_static_stub(test, rv_get_current, stub_rv_get_current);
+ kunit_activate_static_stub(test, rv_current_cpu, stub_rv_current_cpu);
return 0;
}
diff --git a/kernel/trace/rv/rv_monitors_test.h b/kernel/trace/rv/rv_monitors_test.h
index 968b07565a61..3015943c5dda 100644
--- a/kernel/trace/rv/rv_monitors_test.h
+++ b/kernel/trace/rv/rv_monitors_test.h
@@ -23,6 +23,9 @@ struct rv_kunit_ctx {
ctx->expected = ctx->reactions; \
} while (0)
+#define rv_mock_current(ctx, task) (ctx->curr = task)
+#define rv_mock_cpu(ctx, cpu) (ctx->cpu = cpu)
+
#ifdef CONFIG_RV_MON_SCO
extern void rv_test_sco(struct kunit *test);
#else
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 12/12] rv: Add KUnit tests for some LTL monitors
From: Gabriele Monaco @ 2026-04-27 15:11 UTC (permalink / raw)
To: linux-trace-kernel, linux-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260427151134.192971-1-gmonaco@redhat.com>
Validate the functionality of LTL monitors by injecting events in a
controlled environment (KUnit) and expecting reactions, just like it is
done in DA monitors.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/ltl_monitor.h | 31 ++++++++++++++
.../trace/rv/monitors/pagefault/pagefault.c | 24 +++++++++++
kernel/trace/rv/monitors/sleep/sleep.c | 42 +++++++++++++++++++
kernel/trace/rv/rv_monitors_test.c | 2 +
kernel/trace/rv/rv_monitors_test.h | 18 ++++++++
5 files changed, 117 insertions(+)
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index 0f2c3820b9b8..49e680b769f6 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -172,3 +172,34 @@ static void __maybe_unused ltl_atom_pulse(struct task_struct *task, enum ltl_ato
ltl_atom_set(mon, atom, !value);
ltl_validate(task, mon);
}
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include <kunit/test.h>
+
+/*
+ * rv_prepare_test - Disable the monitor for a kunit test
+ */
+static inline void ltl_teardown_test(void *arg)
+{
+ struct rv_monitor *rv_this = arg;
+
+ rv_this->enabled = 0;
+ ltl_monitor_destroy();
+}
+
+/*
+ * rv_prepare_test - Enable the monitor for a kunit test
+ *
+ * Do the bare minimum to set up the monitor, make sure it is not active and
+ * real tracepoint handlers are NOT attached.
+ */
+static inline void ltl_prepare_test(struct kunit *test, struct rv_monitor *rv_this)
+{
+ KUNIT_ASSERT_FALSE(test, rv_this->enabled);
+ ltl_monitor_init();
+ rv_this->enabled = 1;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ kunit_add_action_or_reset(test, ltl_teardown_test, rv_this));
+}
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 56abe5079676..1849d7b81545 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -86,3 +86,27 @@ module_exit(unregister_pagefault);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_pagefault(struct kunit *test)
+{
+ static struct task_struct *target;
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ ltl_prepare_test(test, &rv_pagefault);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 1;
+ handle_task_newtask(NULL, target, 0);
+
+ ltl_attempt_start(target, ltl_get_monitor(target));
+
+ /* RT task has a page fault */
+ rv_mock_current(ctx, target);
+ handle_page_fault(NULL, 0, NULL, 0);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_pagefault);
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 7c16967add70..9388ffb55aa9 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -247,3 +247,45 @@ module_exit(unregister_sleep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep");
+
+#ifdef CONFIG_RV_MONITORS_KUNIT_TEST
+#include "rv_monitors_test.h"
+
+void rv_test_sleep(struct kunit *test)
+{
+ static struct task_struct *target, *other;
+ struct rv_kunit_ctx *ctx = test->priv;
+ unsigned long args[6];
+ struct pt_regs regs;
+
+ ltl_prepare_test(test, &rv_sleep);
+ target = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 2;
+ other = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ other->policy = SCHED_FIFO;
+ other->prio = MAX_RT_PRIO - 1;
+ handle_task_newtask(NULL, target, 0);
+
+ /* RT task sleeps on a non RT-friendly nanosleep */
+ rv_mock_current(ctx, target);
+ args[0] = CLOCK_REALTIME;
+ syscall_set_arguments(target, ®s, args);
+ handle_sys_enter(NULL, ®s, __NR_clock_nanosleep);
+ handle_sys_exit(NULL, NULL, 0);
+ handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+
+ /* RT task woken up by lower priority task */
+ args[1] = FUTEX_WAIT;
+ syscall_set_arguments(target, ®s, args);
+ rv_mock_current(ctx, target);
+ handle_sys_enter(NULL, ®s, __NR_futex);
+ handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_mock_current(ctx, other);
+ handle_sched_waking(NULL, target);
+ handle_sched_wakeup(NULL, target);
+ RV_KUNIT_EXPECT_REACTION(test, ctx);
+}
+EXPORT_SYMBOL_GPL(rv_test_sleep);
+#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index a57a40932d34..78af77310d56 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -81,6 +81,8 @@ static struct kunit_case rv_trigger_test_cases[] = {
KUNIT_CASE(rv_test_sts),
KUNIT_CASE(rv_test_opid),
KUNIT_CASE(rv_test_nomiss),
+ KUNIT_CASE(rv_test_pagefault),
+ KUNIT_CASE(rv_test_sleep),
{}
};
diff --git a/kernel/trace/rv/rv_monitors_test.h b/kernel/trace/rv/rv_monitors_test.h
index 3015943c5dda..c745edc85991 100644
--- a/kernel/trace/rv/rv_monitors_test.h
+++ b/kernel/trace/rv/rv_monitors_test.h
@@ -70,3 +70,21 @@ static inline void rv_test_nomiss(struct kunit *test)
kunit_skip(test, "Monitor not enabled\n");
}
#endif
+
+#ifdef CONFIG_RV_MON_PAGEFAULT
+extern void rv_test_pagefault(struct kunit *test);
+#else
+static inline void rv_test_pagefault(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
+
+#ifdef CONFIG_RV_MON_SLEEP
+extern void rv_test_sleep(struct kunit *test);
+#else
+static inline void rv_test_sleep(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+#endif
--
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