* Re: [PATCH v3 14/17] verification/rvgen: Add selftests for rvgen kunit
From: Nam Cao @ 2026-07-15 11:23 UTC (permalink / raw)
To: Gabriele Monaco, Wen Yang, linux-trace-kernel, linux-kernel
Cc: Steven Rostedt, Thomas Weissschuh, Tomas Glozar, John Kacur
In-Reply-To: <4885b2132ca1e8ed0fd6010ef9506d6e99e58fdb.camel@redhat.com>
Gabriele Monaco <gmonaco@redhat.com> writes:
> Well, I believe a compiler failure was indeed the original intent (that's kinda
> what we do in rv_attach_trace_probe), to avoid unauthored monitors.
>
> Anyway an unauthored monitor isn't as bad as a missing tracepoint, so probably I
> could make the LTL template consistent with the others.
>
> Unless Nam has concerns with that.
No. Go for it.
Nam
^ permalink raw reply
* Re: [PATCH v2] tracing: Propagate errors from remote event bulk updates
From: Vincent Donnefort @ 2026-07-15 9:33 UTC (permalink / raw)
To: Jackie Liu; +Cc: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
In-Reply-To: <20260715074455.3897-1-liu.yun@linux.dev>
On Wed, Jul 15, 2026 at 03:44:55PM +0800, Jackie Liu wrote:
> From: Jackie Liu <liuyun01@kylinos.cn>
>
> remote_events_dir_enable_write() ignores the return value from
> trace_remote_enable_event(). If a remote rejects an event state change,
> the write therefore reports success even though the affected event remains
> in its previous state.
>
> Keep trying all events, but retain and return the first error. This matches
> __ftrace_set_clr_event_nolock(), which permits partial updates while
> notifying userspace when an operation fails.
>
> Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
Thanks!
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
> ---
> Changes in v2:
> - Move the eret declaration after evt to follow reverse Christmas tree
> ordering.
>
> kernel/trace/trace_remote.c | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
> index 0f6ef5c36d84..daeaf1d3eb1c 100644
> --- a/kernel/trace/trace_remote.c
> +++ b/kernel/trace/trace_remote.c
> @@ -1150,10 +1150,21 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
>
> for (i = 0; i < remote->nr_events; i++) {
> struct remote_event *evt = &remote->events[i];
> + int eret;
>
> - trace_remote_enable_event(remote, evt, enable);
> + eret = trace_remote_enable_event(remote, evt, enable);
> + /*
> + * Save the first error and return that. Some events
> + * may still have been enabled, but let the user
> + * know that something went wrong.
> + */
> + if (!ret && eret)
> + ret = eret;
> }
>
> + if (ret)
> + return ret;
> +
> return count;
> }
>
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH v3 14/17] verification/rvgen: Add selftests for rvgen kunit
From: Gabriele Monaco @ 2026-07-15 9:25 UTC (permalink / raw)
To: Wen Yang, linux-trace-kernel, linux-kernel, Nam Cao
Cc: Steven Rostedt, Thomas Weissschuh, Tomas Glozar, John Kacur
In-Reply-To: <a20ddbab-aebe-4f2c-9d07-ec8dee588eab@linux.dev>
On Wed, 2026-07-15 at 01:49 +0800, Wen Yang wrote:
>
> Note that the same typo (handle_sample_event) appears in the other two
> LTL golden files added in patch 05/17 as well: ltl_pertask.c and
> test_ltl.c. The root is likely in the rvgen LTL template itself, so
> the fix there would regenerate all three consistently.
Yeah that's trivial to fix.
> Understood on the intent. One small concern: MODULE_AUTHOR(/* TODO */)
> is subtly different from an XXX comment — a C preprocessor strips the
> comment before macro expansion, leaving MODULE_AUTHOR() with an empty
> argument. When the user does go to compile, they get a cryptic error
> rather than a clear "fill this in" hint.
Well, I believe a compiler failure was indeed the original intent (that's kinda
what we do in rv_attach_trace_probe), to avoid unauthored monitors.
Anyway an unauthored monitor isn't as bad as a missing tracepoint, so probably I
could make the LTL template consistent with the others.
Unless Nam has concerns with that.
Thanks,
Gabriele
^ permalink raw reply
* [PATCH v2] tracing: Add mutex to trace_parser to fix concurrent write races
From: Tengda Wu @ 2026-07-15 8:19 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mark Rutland, Mathieu Desnoyers, linux-trace-kernel, linux-kernel,
Tengda Wu
The trace_parser structure is allocated and initialized when a trace
file is opened, and is subsequently used in the write handler to parse
user input. If userspace opens a trace file descriptor and shares it
across multiple threads, concurrent write calls will race on the
parser's internal state, specifically the idx, cont, and buffer fields,
leading to corrupted input or undefined behavior.
Fix this by embedding a mutex directly in struct trace_parser. The mutex
is initialized in trace_parser_get_init() and destroyed in
trace_parser_put(). All write-side users that access parser state
(trace_get_user() followed by checking trace_parser_loaded() /
trace_parser_cont() against the buffer) now hold the mutex across the
full critical section, avoiding any TOCTOU gap between the parse and the
subsequent consumption of parser->buffer.
Fixes: e704eff3ff51 ("ftrace: Have set_graph_function handle multiple functions in one write")
Fixes: 689fd8b65d66 ("tracing: trace parser support for function and graph")
Cc: stable@vger.kernel.org
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
---
v2: Add proper lockdep assertions to enforce that the parser lock is
held by all callers (Steven).
v1: https://lore.kernel.org/all/20260713134640.708323-1-wutengda@huaweicloud.com/
kernel/trace/ftrace.c | 7 +++++++
kernel/trace/trace.c | 4 ++++
kernel/trace/trace.h | 5 +++++
kernel/trace/trace_events.c | 2 ++
kernel/trace/trace_pid.c | 2 ++
5 files changed, 20 insertions(+)
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index f93e34dd2328..ef47e5659283 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -5842,6 +5842,8 @@ ftrace_regex_write(struct file *file, const char __user *ubuf,
/* iter->hash is a local copy, so we don't need regex_lock */
parser = &iter->parser;
+
+ guard(mutex)(&parser->lock);
read = trace_get_user(parser, ubuf, cnt, ppos);
if (read >= 0 && trace_parser_loaded(parser) &&
@@ -6984,12 +6986,14 @@ int ftrace_regex_release(struct inode *inode, struct file *file)
iter = file->private_data;
parser = &iter->parser;
+ mutex_lock(&parser->lock);
if (trace_parser_loaded(parser)) {
int enable = !(iter->flags & FTRACE_ITER_NOTRACE);
ftrace_process_regex(iter, parser->buffer,
parser->idx, enable);
}
+ mutex_unlock(&parser->lock);
trace_parser_put(parser);
@@ -7321,10 +7325,12 @@ ftrace_graph_release(struct inode *inode, struct file *file)
parser = &fgd->parser;
+ mutex_lock(&parser->lock);
if (trace_parser_loaded((parser))) {
ret = ftrace_graph_set_hash(fgd->new_hash,
parser->buffer);
}
+ mutex_unlock(&parser->lock);
trace_parser_put(parser);
@@ -7437,6 +7443,7 @@ ftrace_graph_write(struct file *file, const char __user *ubuf,
parser = &fgd->parser;
+ guard(mutex)(&parser->lock);
read = trace_get_user(parser, ubuf, cnt, ppos);
if (read >= 0 && trace_parser_loaded(parser) &&
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 1146b83b711a..255432879847 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -1100,6 +1100,7 @@ int trace_parser_get_init(struct trace_parser *parser, int size)
return 1;
parser->size = size;
+ mutex_init(&parser->lock);
return 0;
}
@@ -1108,6 +1109,7 @@ int trace_parser_get_init(struct trace_parser *parser, int size)
*/
void trace_parser_put(struct trace_parser *parser)
{
+ mutex_destroy(&parser->lock);
kfree(parser->buffer);
parser->buffer = NULL;
}
@@ -1130,6 +1132,8 @@ int trace_get_user(struct trace_parser *parser, const char __user *ubuf,
size_t read = 0;
ssize_t ret;
+ lockdep_assert_held(&parser->lock);
+
if (!*ppos)
trace_parser_clear(parser);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 2537c33ddd49..b87baf249eb7 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -1387,26 +1387,31 @@ struct trace_parser {
char *buffer;
unsigned idx;
unsigned size;
+ struct mutex lock;
};
static inline bool trace_parser_loaded(struct trace_parser *parser)
{
+ lockdep_assert_held(&parser->lock);
return !parser->fail && parser->idx != 0;
}
static inline bool trace_parser_cont(struct trace_parser *parser)
{
+ lockdep_assert_held(&parser->lock);
return parser->cont;
}
static inline void trace_parser_clear(struct trace_parser *parser)
{
+ lockdep_assert_held(&parser->lock);
parser->cont = false;
parser->idx = 0;
}
static inline void trace_parser_fail(struct trace_parser *parser)
{
+ lockdep_assert_held(&parser->lock);
parser->fail = true;
}
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index c46e623e7e0d..644e8aad43d4 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1535,6 +1535,7 @@ ftrace_event_write(struct file *file, const char __user *ubuf,
if (trace_parser_get_init(&parser, EVENT_BUF_SIZE + 1))
return -ENOMEM;
+ mutex_lock(&parser.lock);
read = trace_get_user(&parser, ubuf, cnt, ppos);
if (read >= 0 && trace_parser_loaded((&parser))) {
@@ -1551,6 +1552,7 @@ ftrace_event_write(struct file *file, const char __user *ubuf,
ret = read;
out_put:
+ mutex_unlock(&parser.lock);
trace_parser_put(&parser);
return ret;
diff --git a/kernel/trace/trace_pid.c b/kernel/trace/trace_pid.c
index 7127c8de4174..f438291ee3b0 100644
--- a/kernel/trace/trace_pid.c
+++ b/kernel/trace/trace_pid.c
@@ -195,6 +195,7 @@ int trace_pid_write(struct trace_pid_list *filtered_pids,
}
ret = 0;
+ mutex_lock(&parser.lock);
while (cnt > 0) {
pos = 0;
@@ -225,6 +226,7 @@ int trace_pid_write(struct trace_pid_list *filtered_pids,
trace_parser_clear(&parser);
ret = 0;
}
+ mutex_unlock(&parser.lock);
out:
trace_parser_put(&parser);
--
2.34.1
^ permalink raw reply related
* [PATCH v2] tracing: Propagate errors from remote event bulk updates
From: Jackie Liu @ 2026-07-15 7:44 UTC (permalink / raw)
To: vdonnefort, rostedt, mhiramat; +Cc: mathieu.desnoyers, linux-trace-kernel
From: Jackie Liu <liuyun01@kylinos.cn>
remote_events_dir_enable_write() ignores the return value from
trace_remote_enable_event(). If a remote rejects an event state change,
the write therefore reports success even though the affected event remains
in its previous state.
Keep trying all events, but retain and return the first error. This matches
__ftrace_set_clr_event_nolock(), which permits partial updates while
notifying userspace when an operation fails.
Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
---
Changes in v2:
- Move the eret declaration after evt to follow reverse Christmas tree
ordering.
kernel/trace/trace_remote.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 0f6ef5c36d84..daeaf1d3eb1c 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -1150,10 +1150,21 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
for (i = 0; i < remote->nr_events; i++) {
struct remote_event *evt = &remote->events[i];
+ int eret;
- trace_remote_enable_event(remote, evt, enable);
+ eret = trace_remote_enable_event(remote, evt, enable);
+ /*
+ * Save the first error and return that. Some events
+ * may still have been enabled, but let the user
+ * know that something went wrong.
+ */
+ if (!ret && eret)
+ ret = eret;
}
+ if (ret)
+ return ret;
+
return count;
}
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] tracing: ring-buffer: allowlist clang-generated symbols
From: Arnd Bergmann @ 2026-07-15 7:38 UTC (permalink / raw)
To: Steven Rostedt, Vincent Donnefort
Cc: Arnd Bergmann, Masami Hiramatsu, Nathan Chancellor,
Mathieu Desnoyers, Nick Desaulniers, Bill Wendling, Justin Stitt,
Marc Zyngier, Thomas Weißschuh, Paolo Bonzini, linux-kernel,
linux-trace-kernel, llvm
In-Reply-To: <20260714175617.319a6ec7@gandalf.local.home>
On Tue, Jul 14, 2026, at 23:56, Steven Rostedt wrote:
> On Wed, 17 Jun 2026 14:26:36 +0100 Vincent Donnefort <vdonnefort@google.com> wrote:
>
>> On Tue, Jun 16, 2026 at 06:42:03PM +0200, Arnd Bergmann wrote:
>> > From: Arnd Bergmann <arnd@arndb.de>
>> >
>> > In randconfig build testing using clang-22, I came across two
>> > sets of extra symbols in the ring buffer code that may get
>> > inserted by the compiler:
>> >
>> > Unexpected symbols in kernel/trace/simple_ring_buffer.o:
>> > U memset
>> >
>> > Unexpected symbols in kernel/trace/simple_ring_buffer.o:
>> > U llvm_gcda_emit_arcs
>> > U llvm_gcda_emit_function
>> > U llvm_gcda_end_file
>> > U llvm_gcda_start_file
>> > U llvm_gcda_summary_info
>> > U llvm_gcov_init
>> >
>> > Add all of these to the allowlist.
>> >
>> > Signed-off-by: Arnd Bergmann <arnd@arndb.de>
>> > ---
>> > kernel/trace/Makefile | 1 +
>> > 1 file changed, 1 insertion(+)
>> >
>> > diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
>> > index f934ff586bd4..aa8564fb8ff4 100644
>> > --- a/kernel/trace/Makefile
>> > +++ b/kernel/trace/Makefile
>> > @@ -146,6 +146,7 @@ KASAN_SANITIZE_undefsyms_base.o := y
>>
>> Would "GCOV_PROFILE_undefsyms_base.o := y" work?
>
> Arnd?
Yes, turning off gcov for this file should avoid the llvm_gcda and
llvm_gcov symbols, but not the memset() symbol.
All the other features that leave annotations are handled by
listing the symbol names (__asan __gcov __kasan __kcsan __hwasan
__sancov __sanitizer __tsan __ubsan __msan), so I think for
consistency it makes sense to treat llvm gcov the same way
we handle gcc gcov and the rest.
Arnd
^ permalink raw reply
* Re: [PATCH] tracing: Propagate errors from remote event bulk updates
From: Vincent Donnefort @ 2026-07-15 7:25 UTC (permalink / raw)
To: Jackie Liu; +Cc: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
In-Reply-To: <f9050a5ba183da3c9071c5be5172c4e814972836@linux.dev>
On Wed, Jul 15, 2026 at 07:02:01AM +0000, Jackie Liu wrote:
> 2026年7月15日 14:44, "Vincent Donnefort" <vdonnefort@google.com mailto:vdonnefort@google.com?to=%22Vincent%20Donnefort%22%20%3Cvdonnefort%40google.com%3E > 写到:
>
>
> >
> > On Wed, Jul 15, 2026 at 02:04:48PM +0800, Jackie Liu wrote:
> >
> > >
> > > From: Jackie Liu <liuyun01@kylinos.cn>
> > >
> > > remote_events_dir_enable_write() ignores the return value from
> > > trace_remote_enable_event(). If a remote rejects an event state change,
> > > the write therefore reports success even though the affected event remains
> > > in its previous state.
> > >
> > > Keep trying all events, but retain and return the first error. This matches
> > > __ftrace_set_clr_event_nolock(), which permits partial updates while
> > > notifying userspace when an operation fails.
> > >
> > > Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
> > > Assisted-by: Codex:gpt-5.6-sol
> > > Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
> > >
> > I have sent a related improvement for that here [1]
> >
> > [1] https://lore.kernel.org/all/20260605163825.1762953-3-vdonnefort@google.com/
>
> Thanks for the pointer.
>
> I checked the v2 series. Patch 02/18 handles registration failure cleanup,
> and patch 03/18 changes the boolean parsing in
> remote_events_dir_enable_write(), but the loop still ignores errors from
> trace_remote_enable_event().
>
> Would you prefer this fix to be folded into your series, or should I resend
> a v2 rebased on top of it?
Ha yes my bad. Then no, no need to send it separately.
>
> --
> Jackie Liu
>
> >
> > >
> > > ---
> > > kernel/trace/trace_remote.c | 13 ++++++++++++-
> > > 1 file changed, 12 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
> > > index 0f6ef5c36d84..5212e685f0a3 100644
> > > --- a/kernel/trace/trace_remote.c
> > > +++ b/kernel/trace/trace_remote.c
> > > @@ -1149,11 +1149,22 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
> > > guard(mutex)(&remote->lock);
> > >
> > > for (i = 0; i < remote->nr_events; i++) {
> > > + int eret;
> > > struct remote_event *evt = &remote->events[i];
Could you declare ert after? I have tried to use the reverse christmas tree
everywhere in the trace remote code.
> > >
> > > - trace_remote_enable_event(remote, evt, enable);
> > > + eret = trace_remote_enable_event(remote, evt, enable);
> > > + /*
> > > + * Save the first error and return that. Some events
> > > + * may still have been enabled, but let the user
> > > + * know that something went wrong.
> > > + */
> > > + if (!ret && eret)
> > > + ret = eret;
> > > }
> > >
> > > + if (ret)
> > > + return ret;
> > > +
> > > return count;
> > > }
> > >
> > > --
> > > 2.54.0
> > >
> >
^ permalink raw reply
* Re: [PATCH] tracing: Propagate errors from remote event bulk updates
From: Jackie Liu @ 2026-07-15 7:02 UTC (permalink / raw)
To: Vincent Donnefort
Cc: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
In-Reply-To: <alcsNLVLhWOH3KbJ@google.com>
2026年7月15日 14:44, "Vincent Donnefort" <vdonnefort@google.com mailto:vdonnefort@google.com?to=%22Vincent%20Donnefort%22%20%3Cvdonnefort%40google.com%3E > 写到:
>
> On Wed, Jul 15, 2026 at 02:04:48PM +0800, Jackie Liu wrote:
>
> >
> > From: Jackie Liu <liuyun01@kylinos.cn>
> >
> > remote_events_dir_enable_write() ignores the return value from
> > trace_remote_enable_event(). If a remote rejects an event state change,
> > the write therefore reports success even though the affected event remains
> > in its previous state.
> >
> > Keep trying all events, but retain and return the first error. This matches
> > __ftrace_set_clr_event_nolock(), which permits partial updates while
> > notifying userspace when an operation fails.
> >
> > Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
> > Assisted-by: Codex:gpt-5.6-sol
> > Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
> >
> I have sent a related improvement for that here [1]
>
> [1] https://lore.kernel.org/all/20260605163825.1762953-3-vdonnefort@google.com/
Thanks for the pointer.
I checked the v2 series. Patch 02/18 handles registration failure cleanup,
and patch 03/18 changes the boolean parsing in
remote_events_dir_enable_write(), but the loop still ignores errors from
trace_remote_enable_event().
Would you prefer this fix to be folded into your series, or should I resend
a v2 rebased on top of it?
--
Jackie Liu
>
> >
> > ---
> > kernel/trace/trace_remote.c | 13 ++++++++++++-
> > 1 file changed, 12 insertions(+), 1 deletion(-)
> >
> > diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
> > index 0f6ef5c36d84..5212e685f0a3 100644
> > --- a/kernel/trace/trace_remote.c
> > +++ b/kernel/trace/trace_remote.c
> > @@ -1149,11 +1149,22 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
> > guard(mutex)(&remote->lock);
> >
> > for (i = 0; i < remote->nr_events; i++) {
> > + int eret;
> > struct remote_event *evt = &remote->events[i];
> >
> > - trace_remote_enable_event(remote, evt, enable);
> > + eret = trace_remote_enable_event(remote, evt, enable);
> > + /*
> > + * Save the first error and return that. Some events
> > + * may still have been enabled, but let the user
> > + * know that something went wrong.
> > + */
> > + if (!ret && eret)
> > + ret = eret;
> > }
> >
> > + if (ret)
> > + return ret;
> > +
> > return count;
> > }
> >
> > --
> > 2.54.0
> >
>
^ permalink raw reply
* Re: [PATCH v3 4/5] sched: Add task enqueue/dequeue trace points
From: Gabriele Monaco @ 2026-07-15 6:51 UTC (permalink / raw)
To: Steven Rostedt
Cc: Ingo Molnar, Peter Zijlstra, Nam Cao, linux-trace-kernel,
linux-kernel, Juri Lelli, Vincent Guittot, Masami Hiramatsu,
Mathieu Desnoyers, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak
In-Reply-To: <20260714121145.1e73c05e@gandalf.local.home>
On Tue, 2026-07-14 at 12:11 -0400, Steven Rostedt wrote:
> On Wed, 13 Aug 2025 08:50:39 +0200
> Gabriele Monaco <gmonaco@redhat.com> wrote:
>
> > On Mon, 2025-08-11 at 10:40 +0200, Nam Cao wrote:
> > > Add trace points into enqueue_task() and dequeue_task(). They are
> > > useful to implement RV monitor which validates RT scheduling.
> > >
> > > Signed-off-by: Nam Cao <namcao@linutronix.de>
> > > ---
> >
> > Peter, Ingo, this patch adds new tracepoints in the scheduler do agree
> > with the change, can we get an Ack?
>
> Are we still waiting on an Ack for this?
Yes, but since I'm planning to rebase the patches requiring it on other changes
I held this back. I will likely send everything for the next development cycle
after merging those new dependencies.
An Ack is surely welcome also now but not urgent.
Thanks,
Gabriele
>
> I see it in the archive of Patchwork:
>
>
> https://patchwork.kernel.org/project/linux-trace-kernel/list/?series=989907&state=%2A&archive=both
>
> -- Steve
^ permalink raw reply
* Re: [PATCH] tracing: Propagate errors from remote event bulk updates
From: Vincent Donnefort @ 2026-07-15 6:44 UTC (permalink / raw)
To: Jackie Liu; +Cc: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
In-Reply-To: <20260715060448.17674-1-liu.yun@linux.dev>
On Wed, Jul 15, 2026 at 02:04:48PM +0800, Jackie Liu wrote:
> From: Jackie Liu <liuyun01@kylinos.cn>
>
> remote_events_dir_enable_write() ignores the return value from
> trace_remote_enable_event(). If a remote rejects an event state change,
> the write therefore reports success even though the affected event remains
> in its previous state.
>
> Keep trying all events, but retain and return the first error. This matches
> __ftrace_set_clr_event_nolock(), which permits partial updates while
> notifying userspace when an operation fails.
>
> Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
I have sent a related improvement for that here [1]
[1] https://lore.kernel.org/all/20260605163825.1762953-3-vdonnefort@google.com/
> ---
> kernel/trace/trace_remote.c | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
> index 0f6ef5c36d84..5212e685f0a3 100644
> --- a/kernel/trace/trace_remote.c
> +++ b/kernel/trace/trace_remote.c
> @@ -1149,11 +1149,22 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
> guard(mutex)(&remote->lock);
>
> for (i = 0; i < remote->nr_events; i++) {
> + int eret;
> struct remote_event *evt = &remote->events[i];
>
> - trace_remote_enable_event(remote, evt, enable);
> + eret = trace_remote_enable_event(remote, evt, enable);
> + /*
> + * Save the first error and return that. Some events
> + * may still have been enabled, but let the user
> + * know that something went wrong.
> + */
> + if (!ret && eret)
> + ret = eret;
> }
>
> + if (ret)
> + return ret;
> +
> return count;
> }
>
> --
> 2.54.0
>
>
^ permalink raw reply
* Re: [RFC PATCH 08/13] mm/kwatch: add hardware breakpoint backend
From: Jinchao Wang @ 2026-07-15 6:09 UTC (permalink / raw)
To: Steven Rostedt
Cc: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Masami Hiramatsu,
Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
Matthew Wilcox, linux-kernel, linux-mm, linux-trace-kernel,
linux-perf-users, linux-doc
In-Reply-To: <20260714171438.226faf7b@gandalf.local.home>
On 7/14/2026 5:14 PM, Steven Rostedt wrote:
> On Wed, 15 Jul 2026 02:32:06 +0800
> Jinchao Wang <wangjinchao600@gmail.com> wrote:
>
>> --- /dev/null
>> +++ b/include/trace/events/kwatch.h
>> @@ -0,0 +1,57 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#undef TRACE_SYSTEM
>> +#define TRACE_SYSTEM kwatch
>> +
>> +#if !defined(_TRACE_KWATCH_H) || defined(TRACE_HEADER_MULTI_READ)
>> +#define _TRACE_KWATCH_H
>> +
>> +#include <linux/tracepoint.h>
>> +#include <linux/ptrace.h>
>> +
>> +#define KWATCH_STACK_DEPTH 8
>> +
>> +struct trace_seq;
>> +const char *kwatch_trace_print_stack(struct trace_seq *p,
>> + const unsigned long *stack,
>> + unsigned int nr);
>> +
>> +TRACE_EVENT(kwatch_hit,
>> + TP_PROTO(unsigned long ip, unsigned long sp, unsigned long addr,
>> + u64 time_ns,
>> + unsigned long *stack_entries, unsigned int stack_nr),
>> + TP_ARGS(ip, sp, addr, time_ns, stack_entries, stack_nr),
>> +
>> + TP_STRUCT__entry(
>> + __field(unsigned long, ip)
>> + __field(unsigned long, sp)
>> + __field(unsigned long, addr)
>> + __field(u64, time_ns)
>
> Move the time_ns to the first field, as unsigned long on 32 bit
> architectures is 4 bytes, and this will make 4 byte "hole" in the event.
Will fix in v2.>
>
>> + __field(unsigned int, stack_nr)
>
> Make stack_nr the last element for the same reason.
Will fix in v2.
>
>> + __array(unsigned long, stack, KWATCH_STACK_DEPTH)
>
> Make the above a dynamic array based on stack entries.
>
> __dynamic_array(unsigned long, stack, min_t(unsigned int, stack_nr,
> KWATCH_STACK_DEPTH);
Much better than always paying for the full depth - will convert to
__dynamic_array (and use __get_dynamic_array() in TP_fast_assign and
TP_printk as you showed) in v2.
Thank you for the review!
Thanks,
Jinchao
>
>
>> + ),
>> +
>> + TP_fast_assign(
>> + unsigned int i;
> unsigned long *stack = __get_dynamic_array(stack);
>> +
>> + __entry->ip = ip;
>> + __entry->sp = sp;
>> + __entry->addr = addr;
>> + __entry->time_ns = time_ns;
>> + __entry->stack_nr = min_t(unsigned int, stack_nr,
>> + KWATCH_STACK_DEPTH);
>> + for (i = 0; i < __entry->stack_nr; i++)
>> + __entry->stack[i] = stack_entries[i];
>
> stack[i] = stack_entries[i];
>
>> + ),
>> +
>> + TP_printk("KWatch HIT: time=%llu.%06lu ip=%pS addr=0x%lx%s",
>> + __entry->time_ns / 1000000000ULL,
>> + (unsigned long)((__entry->time_ns / 1000ULL) % 1000000ULL),
>> + (void *)__entry->ip, __entry->addr,
>> + kwatch_trace_print_stack(p, __entry->stack,
>
> kwatch_trace_print_stack(p, __get_dynamic_array(stack),
>
>> + __entry->stack_nr))
>> +);
>> +
>
> -- Steve
^ permalink raw reply
* [PATCH] tracing: Propagate errors from remote event bulk updates
From: Jackie Liu @ 2026-07-15 6:04 UTC (permalink / raw)
To: rostedt, mhiramat; +Cc: mathieu.desnoyers, linux-trace-kernel
From: Jackie Liu <liuyun01@kylinos.cn>
remote_events_dir_enable_write() ignores the return value from
trace_remote_enable_event(). If a remote rejects an event state change,
the write therefore reports success even though the affected event remains
in its previous state.
Keep trying all events, but retain and return the first error. This matches
__ftrace_set_clr_event_nolock(), which permits partial updates while
notifying userspace when an operation fails.
Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Jackie Liu <liuyun01@kylinos.cn>
---
kernel/trace/trace_remote.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 0f6ef5c36d84..5212e685f0a3 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -1149,11 +1149,22 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
guard(mutex)(&remote->lock);
for (i = 0; i < remote->nr_events; i++) {
+ int eret;
struct remote_event *evt = &remote->events[i];
- trace_remote_enable_event(remote, evt, enable);
+ eret = trace_remote_enable_event(remote, evt, enable);
+ /*
+ * Save the first error and return that. Some events
+ * may still have been enabled, but let the user
+ * know that something went wrong.
+ */
+ if (!ret && eret)
+ ret = eret;
}
+ if (ret)
+ return ret;
+
return count;
}
--
2.54.0
^ permalink raw reply related
* [PATCH 2/2] rv/nomiss: close enable-time task exit race
From: Li Qiang @ 2026-07-15 6:00 UTC (permalink / raw)
To: linux-trace-kernel
Cc: linux-kernel, rostedt, gmonaco, mhiramat, mathieu.desnoyers,
namcao, juri.lelli, wen.yang, Li Qiang
In-Reply-To: <20260715060023.1502983-1-liqiang01@kylinos.cn>
The initial task scan runs before the first patch registers the task exit
tracepoint. A task can begin exiting after it was selected by the scan but
before the exit handler is attached. sched_process_exit runs before
exit_notify() removes the task from the task list, so the scan could then
store a pointer to the exiting task's deadline entity after its cleanup
event had already been missed.
Initialize the shard state and attach the new-task, exit, and syscall
tracepoints before the initial scan. Make the scan use the same
PF_EXITING-aware creation helper as dynamic policy changes. On failure,
detach those tracepoints before destroying the monitor storage.
Fixes: b133207deb72 ("rv: Add nomiss deadline monitor")
Signed-off-by: Li Qiang <liqiang01@kylinos.cn>
---
kernel/trace/rv/monitors/deadline/deadline.h | 27 ++++++++++-------
kernel/trace/rv/monitors/nomiss/nomiss.c | 18 +++++++----
2 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/kernel/trace/rv/monitors/deadline/deadline.h b/kernel/trace/rv/monitors/deadline/deadline.h
index b92480e79807..4626d6e2b218 100644
--- a/kernel/trace/rv/monitors/deadline/deadline.h
+++ b/kernel/trace/rv/monitors/deadline/deadline.h
@@ -181,6 +181,8 @@ static void deadline_storage_init(void)
}
}
+static int deadline_create_task_storage(struct task_struct *task);
+
/*
* Initialise monitors for all tasks and pre-allocate the storage for servers.
* This is necessary since we don't have access to the servers here and
@@ -190,9 +192,7 @@ static void deadline_storage_init(void)
static inline int init_storage(bool skip_tasks)
{
struct task_struct *g, *p;
- int cpu;
-
- deadline_storage_init();
+ int cpu, ret;
for_each_possible_cpu(cpu) {
if (!da_create_empty_storage(fair_server_id(cpu)))
@@ -208,25 +208,25 @@ static inline int init_storage(bool skip_tasks)
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (p->policy == SCHED_DEADLINE) {
- if (!da_create_storage(EXPAND_ID_TASK(p), NULL)) {
+ ret = deadline_create_task_storage(p);
+ if (ret && ret != -ESRCH) {
read_unlock(&tasklist_lock);
- goto fail;
+ return ret;
}
- atomic_inc(&deadline_storage_shard(p->pid)->users);
}
}
read_unlock(&tasklist_lock);
return 0;
fail:
- da_monitor_destroy();
return -ENOMEM;
}
-static void deadline_create_task_storage(struct task_struct *task)
+static int deadline_create_task_storage(struct task_struct *task)
{
struct deadline_storage_shard *shard = deadline_storage_shard(task->pid);
bool created = false;
+ int ret = -ESRCH;
/*
* The temporary reference makes an in-flight creator visible to exit.
@@ -239,14 +239,21 @@ static void deadline_create_task_storage(struct task_struct *task)
raw_spin_lock(&shard->lock);
if (!(READ_ONCE(task->flags) & PF_EXITING)) {
guard(rcu)();
- if (!da_get_monitor(EXPAND_ID_TASK(task)) &&
- da_create_storage(EXPAND_ID_TASK(task), NULL))
+ if (da_get_monitor(EXPAND_ID_TASK(task))) {
+ ret = 0;
+ } else if (da_create_storage(EXPAND_ID_TASK(task), NULL)) {
created = true;
+ ret = 0;
+ } else {
+ ret = -ENOMEM;
+ }
}
raw_spin_unlock(&shard->lock);
if (!created)
atomic_dec(&shard->users);
+
+ return ret;
}
static void deadline_destroy_task_storage(struct task_struct *task)
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 2e5ee681505b..843262779a41 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -231,18 +231,26 @@ static int enable_nomiss(void)
if (retval)
return retval;
+ deadline_storage_init();
+ rv_attach_trace_probe("nomiss", task_newtask, handle_newtask);
+ rv_attach_trace_probe("nomiss", sched_process_exit, handle_exit);
+ if (!should_skip_syscall_handle())
+ rv_attach_trace_probe("nomiss", sys_enter, handle_sys_enter);
+
retval = init_storage(false);
- if (retval)
+ if (retval) {
+ rv_detach_trace_probe("nomiss", task_newtask, handle_newtask);
+ rv_detach_trace_probe("nomiss", sched_process_exit, handle_exit);
+ if (!should_skip_syscall_handle())
+ rv_detach_trace_probe("nomiss", sys_enter, handle_sys_enter);
+ ha_monitor_destroy();
return retval;
+ }
rv_attach_trace_probe("nomiss", sched_dl_replenish_tp, handle_dl_replenish);
rv_attach_trace_probe("nomiss", sched_dl_throttle_tp, handle_dl_throttle);
rv_attach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
rv_attach_trace_probe("nomiss", sched_switch, handle_sched_switch);
rv_attach_trace_probe("nomiss", sched_wakeup, handle_sched_wakeup);
- if (!should_skip_syscall_handle())
- rv_attach_trace_probe("nomiss", sys_enter, handle_sys_enter);
- rv_attach_trace_probe("nomiss", task_newtask, handle_newtask);
- rv_attach_trace_probe("nomiss", sched_process_exit, handle_exit);
return 0;
}
--
2.50.1
^ permalink raw reply related
* [PATCH 1/2] rv/nomiss: fix task storage lifetime
From: Li Qiang @ 2026-07-15 6:00 UTC (permalink / raw)
To: linux-trace-kernel
Cc: linux-kernel, rostedt, gmonaco, mhiramat, mathieu.desnoyers,
namcao, juri.lelli, wen.yang, Li Qiang
The nomiss monitor stores a pointer to a task's embedded deadline entity
in per-PID object storage. The exit handler only removed that storage when
the task was still SCHED_DEADLINE. A task that changed to another policy
before exiting left its entry behind. Once the PID was reused, the new task
could reuse the old entry and dereference the freed deadline entity.
Remove storage unconditionally at sched_process_exit. Serialize creation
and destruction with locks sharded by PID, and do not create storage once
PF_EXITING is set. Each shard tracks both stored entries and in-flight
creators, allowing exits for shards without storage to avoid locking and
hash lookups while preserving the create/exit ordering.
Fixes: b133207deb72 ("rv: Add nomiss deadline monitor")
Signed-off-by: Li Qiang <liqiang01@kylinos.cn>
---
kernel/trace/rv/monitors/deadline/deadline.h | 86 +++++++++++++++++++-
kernel/trace/rv/monitors/nomiss/nomiss.c | 2 +-
2 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/kernel/trace/rv/monitors/deadline/deadline.h b/kernel/trace/rv/monitors/deadline/deadline.h
index 78fca873d61e..b92480e79807 100644
--- a/kernel/trace/rv/monitors/deadline/deadline.h
+++ b/kernel/trace/rv/monitors/deadline/deadline.h
@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/kernel.h>
+#include <linux/hash.h>
+#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/sched/deadline.h>
#include <asm/syscall.h>
@@ -148,6 +150,37 @@ static inline struct sched_dl_entity *get_server(struct task_struct *tsk, u8 typ
return NULL;
}
+#define DEADLINE_STORAGE_LOCK_BITS 6
+#define DEADLINE_STORAGE_LOCKS BIT(DEADLINE_STORAGE_LOCK_BITS)
+
+/*
+ * A shard tracks both entries and creators which have not yet checked
+ * PF_EXITING. This lets unrelated exits avoid taking a storage lock while
+ * preserving the create/exit ordering.
+ */
+struct deadline_storage_shard {
+ raw_spinlock_t lock;
+ atomic_t users;
+} ____cacheline_aligned_in_smp;
+
+static struct deadline_storage_shard
+ deadline_storage_shards[DEADLINE_STORAGE_LOCKS];
+
+static inline struct deadline_storage_shard *deadline_storage_shard(int pid)
+{
+ return &deadline_storage_shards[hash_32(pid, DEADLINE_STORAGE_LOCK_BITS)];
+}
+
+static void deadline_storage_init(void)
+{
+ int i;
+
+ for (i = 0; i < DEADLINE_STORAGE_LOCKS; i++) {
+ raw_spin_lock_init(&deadline_storage_shards[i].lock);
+ atomic_set(&deadline_storage_shards[i].users, 0);
+ }
+}
+
/*
* Initialise monitors for all tasks and pre-allocate the storage for servers.
* This is necessary since we don't have access to the servers here and
@@ -159,6 +192,8 @@ static inline int init_storage(bool skip_tasks)
struct task_struct *g, *p;
int cpu;
+ deadline_storage_init();
+
for_each_possible_cpu(cpu) {
if (!da_create_empty_storage(fair_server_id(cpu)))
goto fail;
@@ -177,6 +212,7 @@ static inline int init_storage(bool skip_tasks)
read_unlock(&tasklist_lock);
goto fail;
}
+ atomic_inc(&deadline_storage_shard(p->pid)->users);
}
}
read_unlock(&tasklist_lock);
@@ -187,17 +223,61 @@ static inline int init_storage(bool skip_tasks)
return -ENOMEM;
}
+static void deadline_create_task_storage(struct task_struct *task)
+{
+ struct deadline_storage_shard *shard = deadline_storage_shard(task->pid);
+ bool created = false;
+
+ /*
+ * The temporary reference makes an in-flight creator visible to exit.
+ * If storage is created, it becomes the reference owned by that entry.
+ */
+ atomic_inc(&shard->users);
+ /* Pair with exit before it tests users without taking the shard lock. */
+ smp_mb__after_atomic();
+
+ raw_spin_lock(&shard->lock);
+ if (!(READ_ONCE(task->flags) & PF_EXITING)) {
+ guard(rcu)();
+ if (!da_get_monitor(EXPAND_ID_TASK(task)) &&
+ da_create_storage(EXPAND_ID_TASK(task), NULL))
+ created = true;
+ }
+ raw_spin_unlock(&shard->lock);
+
+ if (!created)
+ atomic_dec(&shard->users);
+}
+
+static void deadline_destroy_task_storage(struct task_struct *task)
+{
+ struct deadline_storage_shard *shard = deadline_storage_shard(task->pid);
+
+ /* Pair with the creator barrier before taking the no-storage fast path. */
+ smp_mb();
+ if (!atomic_read(&shard->users))
+ return;
+
+ raw_spin_lock(&shard->lock);
+ guard(rcu)();
+ if (da_get_monitor(task->pid, NULL)) {
+ /* A task may leave SCHED_DEADLINE before exiting. */
+ da_destroy_storage(task->pid);
+ atomic_dec(&shard->users);
+ }
+ raw_spin_unlock(&shard->lock);
+}
+
static void __maybe_unused handle_newtask(void *data, struct task_struct *task, u64 flags)
{
/* Might be superfluous as tasks are not started with this policy.. */
if (task->policy == SCHED_DEADLINE)
- da_create_storage(EXPAND_ID_TASK(task), NULL);
+ deadline_create_task_storage(task);
}
static void __maybe_unused handle_exit(void *data, struct task_struct *p, bool group_dead)
{
- if (p->policy == SCHED_DEADLINE)
- da_destroy_storage(get_entity_id(&p->dl, DL_TASK, DL_TASK));
+ deadline_destroy_task_storage(p);
}
#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 8ead8783c29f..2e5ee681505b 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -214,7 +214,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
if (p->policy == SCHED_DEADLINE)
da_reset(EXPAND_ID_TASK(p));
else if (new_policy == SCHED_DEADLINE)
- da_create_or_get(EXPAND_ID_TASK(p));
+ deadline_create_task_storage(p);
}
static void handle_sched_wakeup(void *data, struct task_struct *tsk)
--
2.50.1
^ permalink raw reply related
* [PATCH v3 2/2] spi: qcom-geni: add GENI SE registers trace event on error paths
From: Praveen Talari @ 2026-07-15 5:20 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Mark Brown
Cc: Praveen Talari, linux-kernel, linux-arm-msm, linux-trace-kernel,
linux-spi, Mukesh Kumar Savaliya, Konrad Dybcio
In-Reply-To: <20260715-add-tracepoints-for-se-reg-dump-v3-0-0f787f93badd@oss.qualcomm.com>
The GENI SPI driver reports various transfer failures such as command
timeouts, DMA reset timeouts, DMA transaction errors, and unexpected
interrupt conditions. However, diagnosing the root cause of these
failures is difficult as the hardware state is not captured when the
error occurs.
Add trace_geni_se_regs() calls at critical SPI error handling paths to
automatically capture GENI serial engine debug registers when failures
are detected. This includes:
- M_CMD abort/cancel timeout
- DMA TX/RX FSM reset timeout
- DMA transaction failures and pending residue conditions
- Unexpected interrupt error status
- Premature transfer completion with pending TX/RX data
Dumping the SE debug registers at the time of failure provides
additional hardware context and significantly improves post-mortem
analysis of SPI transfer issues without affecting normal operation.
Acked-by: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
drivers/spi/spi-geni-qcom.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
index 2914d781dbf5..8528f9b80f03 100644
--- a/drivers/spi/spi-geni-qcom.c
+++ b/drivers/spi/spi-geni-qcom.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+#include <trace/events/qcom_geni_se.h>
+
#define CREATE_TRACE_POINTS
#include <trace/events/qcom_geni_spi.h>
@@ -192,6 +194,7 @@ static void handle_se_timeout(struct spi_controller *spi)
time_left = wait_for_completion_timeout(&mas->abort_done, HZ);
if (!time_left) {
dev_err(mas->dev, "Failed to cancel/abort m_cmd\n");
+ trace_geni_se_regs(se);
/*
* No need for a lock since SPI core has a lock and we never
@@ -209,8 +212,10 @@ static void handle_se_timeout(struct spi_controller *spi)
writel(1, se->base + SE_DMA_TX_FSM_RST);
spin_unlock_irq(&mas->lock);
time_left = wait_for_completion_timeout(&mas->tx_reset_done, HZ);
- if (!time_left)
+ if (!time_left) {
dev_err(mas->dev, "DMA TX RESET failed\n");
+ trace_geni_se_regs(se);
+ }
}
if (xfer->rx_buf) {
spin_lock_irq(&mas->lock);
@@ -218,8 +223,10 @@ static void handle_se_timeout(struct spi_controller *spi)
writel(1, se->base + SE_DMA_RX_FSM_RST);
spin_unlock_irq(&mas->lock);
time_left = wait_for_completion_timeout(&mas->rx_reset_done, HZ);
- if (!time_left)
+ if (!time_left) {
dev_err(mas->dev, "DMA RX RESET failed\n");
+ trace_geni_se_regs(se);
+ }
}
} else {
/*
@@ -391,10 +398,12 @@ static void
spi_gsi_callback_result(void *cb, const struct dmaengine_result *result)
{
struct spi_controller *spi = cb;
+ struct spi_geni_master *mas = spi_controller_get_devdata(spi);
spi->cur_msg->status = -EIO;
if (result->result != DMA_TRANS_NOERROR) {
dev_err(&spi->dev, "DMA txn failed: %d\n", result->result);
+ trace_geni_se_regs(&mas->se);
spi_finalize_current_transfer(spi);
return;
}
@@ -404,6 +413,7 @@ spi_gsi_callback_result(void *cb, const struct dmaengine_result *result)
dev_dbg(&spi->dev, "DMA txn completed\n");
} else {
dev_err(&spi->dev, "DMA xfer has pending: %d\n", result->residue);
+ trace_geni_se_regs(&mas->se);
}
spi_finalize_current_transfer(spi);
@@ -953,8 +963,10 @@ static irqreturn_t geni_spi_isr(int irq, void *data)
if (m_irq & (M_CMD_OVERRUN_EN | M_ILLEGAL_CMD_EN | M_CMD_FAILURE_EN |
M_RX_FIFO_RD_ERR_EN | M_RX_FIFO_WR_ERR_EN |
- M_TX_FIFO_RD_ERR_EN | M_TX_FIFO_WR_ERR_EN))
+ M_TX_FIFO_RD_ERR_EN | M_TX_FIFO_WR_ERR_EN)) {
dev_warn(mas->dev, "Unexpected IRQ err status %#010x\n", m_irq);
+ trace_geni_se_regs(se);
+ }
spin_lock(&mas->lock);
@@ -983,13 +995,16 @@ static irqreturn_t geni_spi_isr(int irq, void *data)
* weren't written correctly.
*/
if (mas->tx_rem_bytes) {
+ trace_geni_se_regs(se);
writel(0, se->base + SE_GENI_TX_WATERMARK_REG);
dev_err(mas->dev, "Premature done. tx_rem = %d bpw%d\n",
mas->tx_rem_bytes, mas->cur_bits_per_word);
}
- if (mas->rx_rem_bytes)
+ if (mas->rx_rem_bytes) {
dev_err(mas->dev, "Premature done. rx_rem = %d bpw%d\n",
mas->rx_rem_bytes, mas->cur_bits_per_word);
+ trace_geni_se_regs(se);
+ }
} else {
complete(&mas->cs_done);
}
--
2.34.1
^ permalink raw reply related
* [PATCH v3 1/2] soc: qcom: geni-se: trace: Add trace event support for GENI SE registers dump
From: Praveen Talari @ 2026-07-15 5:20 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Mark Brown
Cc: Praveen Talari, linux-kernel, linux-arm-msm, linux-trace-kernel,
linux-spi, Mukesh Kumar Savaliya
In-Reply-To: <20260715-add-tracepoints-for-se-reg-dump-v3-0-0f787f93badd@oss.qualcomm.com>
Diagnosing GENI SE-based driver (serial, SPI, I2C) failures currently
requires reading each hardware register individually, either through
ad hoc debug code or a debugger. This is slow, requires the state to
remain stable across the multiple reads, and cannot be run
non-intrusively during normal operation without adding printk-style
noise to each driver.
Add a new trace event header for the Qualcomm GENI Serial Engine (SE)
framework providing a geni_se_regs tracepoint. This tracepoint
captures a comprehensive snapshot of the GENI SE hardware state in a
single trace record, making it possible to correlate register values at
a precise point in time without multiple sequential reads.
The trace event records the following register groups:
- Main/secondary command and IRQ status (M_CMD0, S_CMD0, M/S_IRQ_STATUS)
- Engine status, IOS, and command control/error registers
- TX/RX FIFO status and watermark registers (including RFR watermark)
- M/S GP length registers
- DMA TX/RX IRQ, enable, length, pointer, attribute, and burst registers
- DMA interface enable, general config, QSB trans config, and debug
- M/S IRQ enable, GSI event enable, and top-level SE IRQ enable
- Serial master/slave clock config, general config, output control,
clock control RO, FIFO interface disable, and FW multilock MSA
- Clock select register
Having all these registers captured atomically in a single ftrace record
allows drivers built on top of the GENI SE framework (serial, SPI, I2C)
to invoke this tracepoint on error paths and reconstruct the full engine
state during post-mortem analysis without instrumenting each driver
separately.
Acked-by: Steven Rostedt <rostedt@goodmis.org>
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
drivers/soc/qcom/qcom-geni-se.c | 3 +
include/linux/soc/qcom/geni-se.h | 38 +++++++++
include/trace/events/qcom_geni_se.h | 157 ++++++++++++++++++++++++++++++++++++
3 files changed, 198 insertions(+)
diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c
index 15636a8dc907..44bd730baec6 100644
--- a/drivers/soc/qcom/qcom-geni-se.c
+++ b/drivers/soc/qcom/qcom-geni-se.c
@@ -7,6 +7,9 @@
/* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */
#define __DISABLE_TRACE_MMIO__
+#define CREATE_TRACE_POINTS
+#include <trace/events/qcom_geni_se.h>
+
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h
index c5e6ab85df09..8c08c1917374 100644
--- a/include/linux/soc/qcom/geni-se.h
+++ b/include/linux/soc/qcom/geni-se.h
@@ -81,13 +81,16 @@ struct geni_se {
};
/* Common SE registers */
+#define GENI_GENERAL_CFG 0x10
#define GENI_FORCE_DEFAULT_REG 0x20
#define GENI_OUTPUT_CTRL 0x24
#define SE_GENI_STATUS 0x40
#define GENI_SER_M_CLK_CFG 0x48
#define GENI_SER_S_CLK_CFG 0x4c
+#define GENI_CLK_CTRL_RO 0x60
#define GENI_IF_DISABLE_RO 0x64
#define GENI_FW_REVISION_RO 0x68
+#define GENI_FW_MULTILOCK_MSA_RO 0x74
#define SE_GENI_CLK_SEL 0x7c
#define SE_GENI_CFG_SEQ_START 0x84
#define SE_GENI_DMA_MODE_EN 0x258
@@ -98,6 +101,8 @@ struct geni_se {
#define SE_GENI_M_IRQ_CLEAR 0x618
#define SE_GENI_M_IRQ_EN_SET 0x61c
#define SE_GENI_M_IRQ_EN_CLEAR 0x620
+#define M_CMD_ERR_STATUS 0x624
+#define M_FW_ERR_STATUS 0x628
#define SE_GENI_S_CMD0 0x630
#define SE_GENI_S_CMD_CTRL_REG 0x634
#define SE_GENI_S_IRQ_STATUS 0x640
@@ -115,15 +120,42 @@ struct geni_se {
#define SE_GENI_IOS 0x908
#define SE_GENI_M_GP_LENGTH 0x910
#define SE_GENI_S_GP_LENGTH 0x914
+/* TX DMA registers */
+#define SE_DMA_TX_PTR_L 0xc30
+#define SE_DMA_TX_PTR_H 0xc34
+#define SE_DMA_TX_ATTR 0xc38
+#define SE_DMA_TX_LEN 0xc3c
#define SE_DMA_TX_IRQ_STAT 0xc40
#define SE_DMA_TX_IRQ_CLR 0xc44
+#define SE_DMA_TX_IRQ_EN 0xc48
+#define SE_DMA_TX_IRQ_EN_SET 0xc4c
+#define SE_DMA_TX_IRQ_EN_CLR 0xc50
+#define SE_DMA_TX_LEN_IN 0xc54
#define SE_DMA_TX_FSM_RST 0xc58
+#define SE_DMA_TX_MAX_BURST 0xc5c
+/* RX DMA registers */
+#define SE_DMA_RX_PTR_L 0xd30
+#define SE_DMA_RX_PTR_H 0xd34
+#define SE_DMA_RX_ATTR 0xd38
+#define SE_DMA_RX_LEN 0xd3c
#define SE_DMA_RX_IRQ_STAT 0xd40
#define SE_DMA_RX_IRQ_CLR 0xd44
+#define SE_DMA_RX_IRQ_EN 0xd48
+#define SE_DMA_RX_IRQ_EN_SET 0xd4c
+#define SE_DMA_RX_IRQ_EN_CLR 0xd50
#define SE_DMA_RX_LEN_IN 0xd54
#define SE_DMA_RX_FSM_RST 0xd58
+#define SE_DMA_RX_MAX_BURST 0xd5c
+/* DMA general / debug registers */
+#define SE_GSI_EVENT_EN 0xe18
+#define SE_IRQ_EN 0xe1c
+#define DMA_IF_EN_RO 0xe20
#define SE_HW_PARAM_0 0xe24
#define SE_HW_PARAM_1 0xe28
+#define DMA_GENERAL_CFG 0xe30
+#define SE_DMA_QSB_TRANS_CFG 0xe38
+#define SE_DMA_DEBUG_REG0 0xe40
+#define SE_DMA_IF_EN 0x2004
/* GENI_FORCE_DEFAULT_REG fields */
#define FORCE_DEFAULT BIT(0)
@@ -269,6 +301,12 @@ struct geni_se {
#define RX_GENI_GP_IRQ_EXT GENMASK(13, 12)
#define RX_GENI_CANCEL_IRQ BIT(14)
+/* SE_DMA_DEBUG_REG0 fields */
+#define DMA_TX_ACTIVE BIT(0)
+#define DMA_RX_ACTIVE BIT(1)
+#define DMA_TX_STATE GENMASK(7, 4)
+#define DMA_RX_STATE GENMASK(11, 8)
+
/* SE_HW_PARAM_0 fields */
#define TX_FIFO_WIDTH_MSK GENMASK(29, 24)
#define TX_FIFO_WIDTH_SHFT 24
diff --git a/include/trace/events/qcom_geni_se.h b/include/trace/events/qcom_geni_se.h
new file mode 100644
index 000000000000..4a6e1ba2d147
--- /dev/null
+++ b/include/trace/events/qcom_geni_se.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM qcom_geni_se
+
+#if !defined(_TRACE_QCOM_GENI_SE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_QCOM_GENI_SE_H
+
+#include <linux/io.h>
+#include <linux/tracepoint.h>
+#include <linux/soc/qcom/geni-se.h>
+
+TRACE_EVENT(geni_se_regs,
+ TP_PROTO(struct geni_se *se),
+
+ TP_ARGS(se),
+
+ TP_STRUCT__entry(__string(geni_se_name, dev_name(se->dev))
+ __field(u32, geni_se_m_cmd0)
+ __field(u32, geni_se_m_irq_status)
+ __field(u32, geni_se_s_cmd0)
+ __field(u32, geni_se_s_irq_status)
+ __field(u32, geni_se_status)
+ __field(u32, geni_se_ios)
+ __field(u32, geni_se_m_cmd_ctrl)
+ __field(u32, geni_se_m_cmd_err)
+ __field(u32, geni_se_m_fw_err)
+ __field(u32, geni_se_tx_fifo_status)
+ __field(u32, geni_se_rx_fifo_status)
+ __field(u32, geni_se_tx_watermark)
+ __field(u32, geni_se_rx_watermark)
+ __field(u32, geni_se_rx_watermark_rfr)
+ __field(u32, geni_se_m_gp_length)
+ __field(u32, geni_se_s_gp_length)
+ __field(u32, geni_se_dma_tx_irq)
+ __field(u32, geni_se_dma_rx_irq)
+ __field(u32, geni_se_dma_tx_irq_en)
+ __field(u32, geni_se_dma_rx_irq_en)
+ __field(u32, geni_se_dma_rx_len)
+ __field(u32, geni_se_dma_rx_len_in)
+ __field(u32, geni_se_dma_tx_len)
+ __field(u32, geni_se_dma_tx_len_in)
+ __field(u32, geni_se_dma_tx_ptr_l)
+ __field(u32, geni_se_dma_tx_ptr_h)
+ __field(u32, geni_se_dma_rx_ptr_l)
+ __field(u32, geni_se_dma_rx_ptr_h)
+ __field(u32, geni_se_dma_tx_attr)
+ __field(u32, geni_se_dma_tx_max_burst)
+ __field(u32, geni_se_dma_rx_attr)
+ __field(u32, geni_se_dma_rx_max_burst)
+ __field(u32, geni_se_dma_if_en)
+ __field(u32, geni_se_dma_if_en_ro)
+ __field(u32, geni_se_dma_general_cfg)
+ __field(u32, geni_se_dma_qsb_trans_cfg)
+ __field(u32, geni_se_dma_dbg)
+ __field(u32, geni_se_m_irq_en)
+ __field(u32, geni_se_s_irq_en)
+ __field(u32, geni_se_gsi_event_en)
+ __field(u32, geni_se_irq_en)
+ __field(u32, geni_se_ser_m_clk_cfg)
+ __field(u32, geni_se_ser_s_clk_cfg)
+ __field(u32, geni_se_general_cfg)
+ __field(u32, geni_se_output_ctrl)
+ __field(u32, geni_se_clk_ctrl_ro)
+ __field(u32, geni_se_fifo_if_disable)
+ __field(u32, geni_se_fw_multilock_msa)
+ __field(u32, geni_se_clk_sel)
+ ),
+
+ TP_fast_assign(__assign_str(geni_se_name);
+ __entry->geni_se_m_cmd0 = readl(se->base + SE_GENI_M_CMD0);
+ __entry->geni_se_m_irq_status = readl(se->base + SE_GENI_M_IRQ_STATUS);
+ __entry->geni_se_s_cmd0 = readl(se->base + SE_GENI_S_CMD0);
+ __entry->geni_se_s_irq_status = readl(se->base + SE_GENI_S_IRQ_STATUS);
+ __entry->geni_se_status = readl(se->base + SE_GENI_STATUS);
+ __entry->geni_se_ios = readl(se->base + SE_GENI_IOS);
+ __entry->geni_se_m_cmd_ctrl = readl(se->base + SE_GENI_M_CMD_CTRL_REG);
+ __entry->geni_se_m_cmd_err = readl(se->base + M_CMD_ERR_STATUS);
+ __entry->geni_se_m_fw_err = readl(se->base + M_FW_ERR_STATUS);
+ __entry->geni_se_tx_fifo_status = readl(se->base + SE_GENI_TX_FIFO_STATUS);
+ __entry->geni_se_rx_fifo_status = readl(se->base + SE_GENI_RX_FIFO_STATUS);
+ __entry->geni_se_tx_watermark = readl(se->base + SE_GENI_TX_WATERMARK_REG);
+ __entry->geni_se_rx_watermark = readl(se->base + SE_GENI_RX_WATERMARK_REG);
+ __entry->geni_se_rx_watermark_rfr = readl(se->base + SE_GENI_RX_RFR_WATERMARK_REG);
+ __entry->geni_se_m_gp_length = readl(se->base + SE_GENI_M_GP_LENGTH);
+ __entry->geni_se_s_gp_length = readl(se->base + SE_GENI_S_GP_LENGTH);
+ __entry->geni_se_dma_tx_irq = readl(se->base + SE_DMA_TX_IRQ_STAT);
+ __entry->geni_se_dma_rx_irq = readl(se->base + SE_DMA_RX_IRQ_STAT);
+ __entry->geni_se_dma_tx_irq_en = readl(se->base + SE_DMA_TX_IRQ_EN);
+ __entry->geni_se_dma_rx_irq_en = readl(se->base + SE_DMA_RX_IRQ_EN);
+ __entry->geni_se_dma_rx_len = readl(se->base + SE_DMA_RX_LEN);
+ __entry->geni_se_dma_rx_len_in = readl(se->base + SE_DMA_RX_LEN_IN);
+ __entry->geni_se_dma_tx_len = readl(se->base + SE_DMA_TX_LEN);
+ __entry->geni_se_dma_tx_len_in = readl(se->base + SE_DMA_TX_LEN_IN);
+ __entry->geni_se_dma_tx_ptr_l = readl(se->base + SE_DMA_TX_PTR_L);
+ __entry->geni_se_dma_tx_ptr_h = readl(se->base + SE_DMA_TX_PTR_H);
+ __entry->geni_se_dma_rx_ptr_l = readl(se->base + SE_DMA_RX_PTR_L);
+ __entry->geni_se_dma_rx_ptr_h = readl(se->base + SE_DMA_RX_PTR_H);
+ __entry->geni_se_dma_tx_attr = readl(se->base + SE_DMA_TX_ATTR);
+ __entry->geni_se_dma_tx_max_burst = readl(se->base + SE_DMA_TX_MAX_BURST);
+ __entry->geni_se_dma_rx_attr = readl(se->base + SE_DMA_RX_ATTR);
+ __entry->geni_se_dma_rx_max_burst = readl(se->base + SE_DMA_RX_MAX_BURST);
+ __entry->geni_se_dma_if_en = readl(se->base + SE_DMA_IF_EN);
+ __entry->geni_se_dma_if_en_ro = readl(se->base + DMA_IF_EN_RO);
+ __entry->geni_se_dma_general_cfg = readl(se->base + DMA_GENERAL_CFG);
+ __entry->geni_se_dma_qsb_trans_cfg = readl(se->base + SE_DMA_QSB_TRANS_CFG);
+ __entry->geni_se_dma_dbg = readl(se->base + SE_DMA_DEBUG_REG0);
+ __entry->geni_se_m_irq_en = readl(se->base + SE_GENI_M_IRQ_EN);
+ __entry->geni_se_s_irq_en = readl(se->base + SE_GENI_S_IRQ_EN);
+ __entry->geni_se_gsi_event_en = readl(se->base + SE_GSI_EVENT_EN);
+ __entry->geni_se_irq_en = readl(se->base + SE_IRQ_EN);
+ __entry->geni_se_ser_m_clk_cfg = readl(se->base + GENI_SER_M_CLK_CFG);
+ __entry->geni_se_ser_s_clk_cfg = readl(se->base + GENI_SER_S_CLK_CFG);
+ __entry->geni_se_general_cfg = readl(se->base + GENI_GENERAL_CFG);
+ __entry->geni_se_output_ctrl = readl(se->base + GENI_OUTPUT_CTRL);
+ __entry->geni_se_clk_ctrl_ro = readl(se->base + GENI_CLK_CTRL_RO);
+ __entry->geni_se_fifo_if_disable = readl(se->base + GENI_IF_DISABLE_RO);
+ __entry->geni_se_fw_multilock_msa = readl(se->base + GENI_FW_MULTILOCK_MSA_RO);
+ __entry->geni_se_clk_sel = readl(se->base + SE_GENI_CLK_SEL);
+ ),
+
+ TP_printk("%s: m_cmd0=0x%08x m_irq_status=0x%08x s_cmd0=0x%08x s_irq_status=0x%08x geni_status=0x%08x geni_ios=0x%08x m_cmd_ctrl=0x%08x m_cmd_err=0x%08x m_fw_err=0x%08x tx_fifo_sts=0x%08x rx_fifo_sts=0x%08x tx_watermark=0x%08x rx_watermark=0x%08x rx_watermark_rfr=0x%08x m_gp_length=0x%08x s_gp_length=0x%08x dma_tx_irq=0x%08x dma_rx_irq=0x%08x dma_tx_irq_en=0x%08x dma_rx_irq_en=0x%08x dma_rx_len=0x%08x dma_rx_len_in=0x%08x dma_tx_len=0x%08x dma_tx_len_in=0x%08x dma_tx_ptr_l=0x%08x dma_tx_ptr_h=0x%08x dma_rx_ptr_l=0x%08x dma_rx_ptr_h=0x%08x dma_tx_attr=0x%08x dma_tx_max_burst=0x%08x dma_rx_attr=0x%08x dma_rx_max_burst=0x%08x dma_if_en=0x%08x dma_if_en_ro=0x%08x dma_general_cfg=0x%08x dma_qsb_trans_cfg=0x%08x dma_dbg=0x%08x m_irq_en=0x%08x s_irq_en=0x%08x gsi_event_en=0x%08x se_irq_en=0x%08x ser_m_clk_cfg=0x%08x ser_s_clk_cfg=0x%08x general_cfg=0x%08x output_ctrl=0x%08x clk_ctrl_ro=0x%08x fifo_if_dis=0x%08x fw_multilock_msa=0x%08x clk_sel=0x%08x",
+ __get_str(geni_se_name),
+ __entry->geni_se_m_cmd0, __entry->geni_se_m_irq_status,
+ __entry->geni_se_s_cmd0, __entry->geni_se_s_irq_status,
+ __entry->geni_se_status, __entry->geni_se_ios,
+ __entry->geni_se_m_cmd_ctrl,
+ __entry->geni_se_m_cmd_err, __entry->geni_se_m_fw_err,
+ __entry->geni_se_tx_fifo_status, __entry->geni_se_rx_fifo_status,
+ __entry->geni_se_tx_watermark, __entry->geni_se_rx_watermark,
+ __entry->geni_se_rx_watermark_rfr,
+ __entry->geni_se_m_gp_length, __entry->geni_se_s_gp_length,
+ __entry->geni_se_dma_tx_irq, __entry->geni_se_dma_rx_irq,
+ __entry->geni_se_dma_tx_irq_en, __entry->geni_se_dma_rx_irq_en,
+ __entry->geni_se_dma_rx_len, __entry->geni_se_dma_rx_len_in,
+ __entry->geni_se_dma_tx_len, __entry->geni_se_dma_tx_len_in,
+ __entry->geni_se_dma_tx_ptr_l, __entry->geni_se_dma_tx_ptr_h,
+ __entry->geni_se_dma_rx_ptr_l, __entry->geni_se_dma_rx_ptr_h,
+ __entry->geni_se_dma_tx_attr, __entry->geni_se_dma_tx_max_burst,
+ __entry->geni_se_dma_rx_attr, __entry->geni_se_dma_rx_max_burst,
+ __entry->geni_se_dma_if_en, __entry->geni_se_dma_if_en_ro,
+ __entry->geni_se_dma_general_cfg, __entry->geni_se_dma_qsb_trans_cfg,
+ __entry->geni_se_dma_dbg,
+ __entry->geni_se_m_irq_en, __entry->geni_se_s_irq_en,
+ __entry->geni_se_gsi_event_en, __entry->geni_se_irq_en,
+ __entry->geni_se_ser_m_clk_cfg, __entry->geni_se_ser_s_clk_cfg,
+ __entry->geni_se_general_cfg, __entry->geni_se_output_ctrl,
+ __entry->geni_se_clk_ctrl_ro, __entry->geni_se_fifo_if_disable,
+ __entry->geni_se_fw_multilock_msa, __entry->geni_se_clk_sel)
+);
+
+#endif /* _TRACE_QCOM_GENI_SE_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
--
2.34.1
^ permalink raw reply related
* [PATCH v3 0/2] Add trace event support for GENI SE registers dump
From: Praveen Talari @ 2026-07-15 5:20 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Mark Brown
Cc: Praveen Talari, linux-kernel, linux-arm-msm, linux-trace-kernel,
linux-spi, Mukesh Kumar Savaliya, Konrad Dybcio
The GENI framework is used by multiple drivers including UART, I2C, and
SPI. When hardware-related failures occur, each driver typically relies
on local logging, which often lacks sufficient information to determine
the exact controller state.
This series introduces a common tracing mechanism for GENI Serial Engine
debug registers and demonstrates its use in the SPI driver.
Patch 1 adds a new tracepoint that captures an extensive set of GENI SE
registers, including command state, interrupt status, FIFO state, DMA
configuration, and clock-related information.
Patch 2 hooks the tracepoint into SPI error paths so that register
snapshots are automatically generated when timeouts or transfer-related
failures occur.
Usage examples:
Enable all I2C traces:
echo 1 > /sys/kernel/tracing/events/qcom_geni_se/enable
cat /sys/kernel/debug/tracing/trace_pipe
Example trace output:
114.291299: geni_se_regs: 888000.spi: m_cmd0=0x18000000
m_irq_status=0x00000080 s_cmd0=0x00000000 s_irq_status=0x08000000
geni_status=0x00000000 geni_ios=0x00000000 m_cmd_ctrl=0x00000000
m_cmd_err=0x00000000 m_fw_err=0x00000000 tx_fifo_sts=0x00000000
rx_fifo_sts=0x00000000 tx_watermark=0x00000000 rx_watermark=0x0000000d
rx_watermark_rfr=0x0000000e m_gp_length=0x00000004 s_gp_length=0x00000000
dma_tx_irq=0x00000000 dma_rx_irq=0x00000000 dma_tx_irq_en=0x0000000f
dma_rx_irq_en=0x0000001f dma_rx_len=0x00001400 dma_rx_len_in=0x00001400
dma_tx_len=0x00001400 dma_tx_len_in=0x00001400 dma_tx_ptr_l=0xffffc000
dma_tx_ptr_h=0x00000000 dma_rx_ptr_l=0xffffa000 dma_rx_ptr_h=0x00000000
dma_tx_attr=0x00000001 dma_tx_max_burst=0x00000002 dma_rx_attr=0x00000000
dma_rx_max_burst=0x00000002 dma_if_en=0x00000009 dma_if_en_ro=0x00000001
dma_general_cfg=0x0000000f dma_qsb_trans_cfg=0x00000000 dma_dbg=0x00000000
m_irq_en=0x7fc0007f s_irq_en=0x03003e3e gsi_event_en=0x00000000
se_irq_en=0x0000000f ser_m_clk_cfg=0x000000a1 ser_s_clk_cfg=0x00000000
general_cfg=0x00000048 output_ctrl=0x0000007f clk_ctrl_ro=0x00000001
fifo_if_dis=0x00000000 fw_multilock_msa=0x00000000 clk_sel=0x00000005
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
Changes in v3:
- Trace se registers before clearing tx watermark reg.
- Updated commit text.
- Link to v2: https://lore.kernel.org/all/20260711-add-tracepoints-for-se-reg-dump-v2-0-ca1e9ba62359@oss.qualcomm.com
Changes in v2:
- Updated SE_DMA_IF_EN to correct value.
- Sorted hearders.
- Link to v1: https://lore.kernel.org/all/20260706-add-tracepoints-for-se-reg-dump-v1-0-48bd08e28cf2@oss.qualcomm.com
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
To: Steven Rostedt <rostedt@goodmis.org>
To: Masami Hiramatsu <mhiramat@kernel.org>
To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Mark Brown <broonie@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-trace-kernel@vger.kernel.org
Cc: linux-spi@vger.kernel.org
Cc: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
---
Praveen Talari (2):
soc: qcom: geni-se: trace: Add trace event support for GENI SE registers dump
spi: qcom-geni: add GENI SE registers trace event on error paths
drivers/soc/qcom/qcom-geni-se.c | 3 +
drivers/spi/spi-geni-qcom.c | 23 +++++-
include/linux/soc/qcom/geni-se.h | 38 +++++++++
include/trace/events/qcom_geni_se.h | 157 ++++++++++++++++++++++++++++++++++++
4 files changed, 217 insertions(+), 4 deletions(-)
---
base-commit: bee763d5f341b99cf472afeb508d4988f62a6ca1
change-id: 20260630-add-tracepoints-for-se-reg-dump-c2c8bc875658
Best regards,
--
Praveen Talari <praveen.talari@oss.qualcomm.com>
^ permalink raw reply
* Re: [RFC PATCH v4 1/3] trace: add lock-free stackmap for stack trace deduplication
From: Li Pengfei @ 2026-07-15 3:12 UTC (permalink / raw)
To: rostedt, mhiramat
Cc: mathieu.desnoyers, mark.rutland, linux-trace-kernel, linux-kernel,
zhangbo56, lipengfei28
In-Reply-To: <20260714171144.4537f163@gandalf.local.home>
From: Pengfei Li <lipengfei28@xiaomi.com>
On Tue, 14 Jul 2026 17:11:44 -0400 Steven Rostedt wrote:
> smap->entries = vcalloc(smap->map_size, sizeof(*smap->entries));
> Make the error paths have: ... goto fail; ...
Will switch both entries and elts to vcalloc(), and collapse the
error paths into a single goto-fail ladder (freeing in reverse
alloc order, including the drops percpu).
> Do not add anonymous blocks in functions.
Will move the cpu declaration to the top of ftrace_stackmap_reset().
> Really should have ftrace_stackmap_bin_entry have a flexible
> array: u64 ips[];
Agreed - will add the u64 ips[] flexible member and use
struct_size(e, ips, nr) in stackmap_bin_open(). On-disk layout is
unchanged, so the bin format stays compatible.
I'll also fold in two issues I found locally: a missing lock on one
init failure path, and TRACE_STACK_ID not being handled in the
function_graph output (it falls through to print_graph_comment()
instead of being punted like TRACE_STACK).
One design point I'd like your steer on before respinning: reset
checks tracer_tracing_is_on() once under trace_types_lock, but
traceon triggers (ftrace_traceon / traceon_trigger) can re-enable
tracing without that lock during reset's clear phase. As far as I
can tell this is a semantic-contract issue, not a memory-safety
one: the map is protected by the resetting flag (get_id bails with
-EINVAL and synchronize_rcu() drains in-flight callers), and the
ring buffer pages aren't freed by reset, so the worst case is a
non-empty / inconsistent buffer after reset rather than corruption.
So I'm leaning toward documenting reset as best-effort ("stop
tracing, including traceon triggers, before reset") rather than
adding machinery to block the window. Does that match your view, or
is there a ring-buffer-state hazard I'm missing that would justify
blocking it explicitly?
On the element pool: your `-l '*lock*'` run is a good illustration -
the ~326K-line stack_map dump is the 16K-entry pool (bits=14)
filling up quickly under broad function tracing. I'm leaning toward
keeping eager allocation for v5 (it keeps the hot path free of an
allocation-failure path), but I'm happy to switch to lazy allocation
on the first 'echo 1 > options/stackmap' if you'd rather not pay the
~8MB resident cost when the option is never enabled. I'd keep the
stack_map_bin interface as-is.
Thanks,
Pengfei
^ permalink raw reply
* [PATCH v2] rv: Simplify task monitor slot management
From: Li Qiang @ 2026-07-15 1:58 UTC (permalink / raw)
To: linux-trace-kernel
Cc: rostedt, gmonaco, mhiramat, mathieu.desnoyers, linux-kernel
In-Reply-To: <3ea6ffdd144ce4b38d4a198550afed9516777f85.camel@redhat.com>
The slot array already tracks allocation and task_monitor_count
duplicates that state. On an invalid second release, the old code
warns but still decrements the counter, corrupting later allocations.
Use the slot array as the sole source of truth. Return after warning
about an unused slot, and return -EBUSY when no slot is free.
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Li Qiang <liqiang01@kylinos.cn>
---
kernel/trace/rv/rv.c | 18 +++++-------------
1 file changed, 5 insertions(+), 13 deletions(-)
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index ee4e68102f17..187d87d5991c 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -164,7 +164,6 @@ struct dentry *get_monitors_root(void)
*/
LIST_HEAD(rv_monitors_list);
-static int task_monitor_count;
static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS];
int rv_get_task_monitor_slot(void)
@@ -173,21 +172,14 @@ int rv_get_task_monitor_slot(void)
lockdep_assert_held(&rv_interface_lock);
- if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
- return -EBUSY;
-
- task_monitor_count++;
-
for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) {
- if (task_monitor_slots[i] == false) {
+ if (!task_monitor_slots[i]) {
task_monitor_slots[i] = true;
return i;
}
}
- WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n");
-
- return -EINVAL;
+ return -EBUSY;
}
void rv_put_task_monitor_slot(int slot)
@@ -199,10 +191,10 @@ void rv_put_task_monitor_slot(int slot)
return;
}
- WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n",
- slot);
+ if (WARN_ONCE(!task_monitor_slots[slot],
+ "RV releasing unused task monitor slot: %d\n", slot))
+ return;
- task_monitor_count--;
task_monitor_slots[slot] = false;
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] selftests/ftrace: Add test case for a symbol in a module without module name
From: Masami Hiramatsu @ 2026-07-15 1:58 UTC (permalink / raw)
To: Steven Rostedt
Cc: Shuah Khan, Andrii Nakryiko, linux-trace-kernel, bpf,
Francis Laniel, linux-kselftest
In-Reply-To: <20260714104210.28cc4c59@gandalf.local.home>
OK, this seems good to me, and tested. Let me pick it to probes/for-next.
Thanks!
On Tue, 14 Jul 2026 10:42:10 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
>
> Is this still a think, or can we remove it from patchwork?
>
> https://patchwork.kernel.org/project/linux-trace-kernel/patch/169846405196.88147.17766692778800222203.stgit@devnote2/
>
> -- Steve
>
>
> On Sat, 28 Oct 2023 12:34:12 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> >
> > Add a test case for probing on a symbol in a module without module name.
> > When probing on a symbol in a module, ftrace accepts both the syntax that
> > <MODNAME>:<SYMBOL> and <SYMBOL>. Current test case only checks the former
> > syntax. This adds a test for the latter one.
> >
> > Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> > ---
> > .../ftrace/test.d/kprobe/kprobe_module.tc | 6 ++++++
> > 1 file changed, 6 insertions(+)
> >
> > diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > index 7e74ee11edf9..4b32e1b9a8d3 100644
> > --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > @@ -13,6 +13,12 @@ fi
> > MOD=trace_printk
> > FUNC=trace_printk_irq_work
> >
> > +:;: "Add an event on a module function without module name" ;:
> > +
> > +echo "p:event0 $FUNC" > kprobe_events
> > +test -d events/kprobes/event0 || exit_failure
> > +echo "-:kprobes/event0" >> kprobe_events
> > +
> > :;: "Add an event on a module function without specifying event name" ;:
> >
> > echo "p $MOD:$FUNC" > kprobe_events
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* Re: [PATCH] selftests/ftrace: Add test case for a symbol in a module without module name
From: Masami Hiramatsu @ 2026-07-15 1:55 UTC (permalink / raw)
To: Steven Rostedt
Cc: Shuah Khan, Andrii Nakryiko, linux-trace-kernel, bpf,
Francis Laniel, linux-kselftest
In-Reply-To: <20260714104210.28cc4c59@gandalf.local.home>
On Tue, 14 Jul 2026 10:42:10 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
>
> Is this still a think, or can we remove it from patchwork?
>
> https://patchwork.kernel.org/project/linux-trace-kernel/patch/169846405196.88147.17766692778800222203.stgit@devnote2/
Oops, thanks for reminding. Let me check.
>
> -- Steve
>
>
> On Sat, 28 Oct 2023 12:34:12 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> >
> > Add a test case for probing on a symbol in a module without module name.
> > When probing on a symbol in a module, ftrace accepts both the syntax that
> > <MODNAME>:<SYMBOL> and <SYMBOL>. Current test case only checks the former
> > syntax. This adds a test for the latter one.
> >
> > Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> > ---
> > .../ftrace/test.d/kprobe/kprobe_module.tc | 6 ++++++
> > 1 file changed, 6 insertions(+)
> >
> > diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > index 7e74ee11edf9..4b32e1b9a8d3 100644
> > --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_module.tc
> > @@ -13,6 +13,12 @@ fi
> > MOD=trace_printk
> > FUNC=trace_printk_irq_work
> >
> > +:;: "Add an event on a module function without module name" ;:
> > +
> > +echo "p:event0 $FUNC" > kprobe_events
> > +test -d events/kprobes/event0 || exit_failure
> > +echo "-:kprobes/event0" >> kprobe_events
> > +
> > :;: "Add an event on a module function without specifying event name" ;:
> >
> > echo "p $MOD:$FUNC" > kprobe_events
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* Re: [PATCH] bpf/btf: Move tracing BTF APIs to the BTF library
From: Masami Hiramatsu @ 2026-07-15 1:53 UTC (permalink / raw)
To: Steven Rostedt
Cc: Martin KaFai Lau, Alexei Starovoitov, linux-trace-kernel, bpf,
linux-kernel
In-Reply-To: <20260714103401.0aa3f9e0@gandalf.local.home>
On Tue, 14 Jul 2026 10:34:01 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
>
> Whatever happened to this patch?
IIRC, this got rejected, so we ended up having
to keep maintaining it within the trace.
(because bpf does not use these functions.)
Thanks,
>
> -- Steve
>
>
> On Tue, 10 Oct 2023 22:54:19 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> >
> > Move the BTF APIs used in tracing to the BTF library code for sharing it
> > with others.
> > Previously, to avoid complex dependency in a series I made it on the
> > tracing tree, but now it is a good time to move it to BPF tree because
> > these functions are pure BTF functions.
> >
> > Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> > ---
> > include/linux/btf.h | 24 +++++++++
> > kernel/bpf/btf.c | 115 +++++++++++++++++++++++++++++++++++++++++
> > kernel/trace/Makefile | 1
> > kernel/trace/trace_btf.c | 122 --------------------------------------------
> > kernel/trace/trace_btf.h | 11 ----
> > kernel/trace/trace_probe.c | 2 -
> > 6 files changed, 140 insertions(+), 135 deletions(-)
> > delete mode 100644 kernel/trace/trace_btf.c
> > delete mode 100644 kernel/trace/trace_btf.h
> >
> > diff --git a/include/linux/btf.h b/include/linux/btf.h
> > index 928113a80a95..8372d93ea402 100644
> > --- a/include/linux/btf.h
> > +++ b/include/linux/btf.h
> > @@ -507,6 +507,14 @@ btf_get_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf,
> > int get_kern_ctx_btf_id(struct bpf_verifier_log *log, enum bpf_prog_type prog_type);
> > bool btf_types_are_same(const struct btf *btf1, u32 id1,
> > const struct btf *btf2, u32 id2);
> > +const struct btf_type *btf_find_func_proto(const char *func_name,
> > + struct btf **btf_p);
> > +const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
> > + s32 *nr);
> > +const struct btf_member *btf_find_struct_member(struct btf *btf,
> > + const struct btf_type *type,
> > + const char *member_name,
> > + u32 *anon_offset);
> > #else
> > static inline const struct btf_type *btf_type_by_id(const struct btf *btf,
> > u32 type_id)
> > @@ -559,6 +567,22 @@ static inline bool btf_types_are_same(const struct btf *btf1, u32 id1,
> > {
> > return false;
> > }
> > +static inline const struct btf_type *btf_find_func_proto(const char *func_name,
> > + struct btf **btf_p)
> > +{
> > + return NULL;
> > +}
> > +static inline const struct btf_param *
> > +btf_get_func_param(const struct btf_type *func_proto, s32 *nr)
> > +{
> > + return NULL;
> > +}
> > +static inline const struct btf_member *
> > +btf_find_struct_member(struct btf *btf, const struct btf_type *type,
> > + const char *member_name, u32 *anon_offset)
> > +{
> > + return NULL;
> > +}
> > #endif
> >
> > static inline bool btf_type_is_struct_ptr(struct btf *btf, const struct btf_type *t)
> > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> > index 8090d7fb11ef..e5cbf3b31b78 100644
> > --- a/kernel/bpf/btf.c
> > +++ b/kernel/bpf/btf.c
> > @@ -912,6 +912,121 @@ static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf,
> > return t;
> > }
> >
> > +/*
> > + * Find a function proto type by name, and return the btf_type with its btf
> > + * in *@btf_p. Return NULL if not found.
> > + * Note that caller has to call btf_put(*@btf_p) after using the btf_type.
> > + */
> > +const struct btf_type *btf_find_func_proto(const char *func_name, struct btf **btf_p)
> > +{
> > + const struct btf_type *t;
> > + s32 id;
> > +
> > + id = bpf_find_btf_id(func_name, BTF_KIND_FUNC, btf_p);
> > + if (id < 0)
> > + return NULL;
> > +
> > + /* Get BTF_KIND_FUNC type */
> > + t = btf_type_by_id(*btf_p, id);
> > + if (!t || !btf_type_is_func(t))
> > + goto err;
> > +
> > + /* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
> > + t = btf_type_by_id(*btf_p, t->type);
> > + if (!t || !btf_type_is_func_proto(t))
> > + goto err;
> > +
> > + return t;
> > +err:
> > + btf_put(*btf_p);
> > + return NULL;
> > +}
> > +
> > +/*
> > + * Get function parameter with the number of parameters.
> > + * This can return NULL if the function has no parameters.
> > + * It can return -EINVAL if the @func_proto is not a function proto type.
> > + */
> > +const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, s32 *nr)
> > +{
> > + if (!btf_type_is_func_proto(func_proto))
> > + return ERR_PTR(-EINVAL);
> > +
> > + *nr = btf_type_vlen(func_proto);
> > + if (*nr > 0)
> > + return (const struct btf_param *)(func_proto + 1);
> > + else
> > + return NULL;
> > +}
> > +
> > +#define BTF_ANON_STACK_MAX 16
> > +
> > +struct btf_anon_stack {
> > + u32 tid;
> > + u32 offset;
> > +};
> > +
> > +/*
> > + * Find a member of data structure/union by name and return it.
> > + * Return NULL if not found, or -EINVAL if parameter is invalid.
> > + * If the member is an member of anonymous union/structure, the offset
> > + * of that anonymous union/structure is stored into @anon_offset. Caller
> > + * can calculate the correct offset from the root data structure by
> > + * adding anon_offset to the member's offset.
> > + */
> > +const struct btf_member *btf_find_struct_member(struct btf *btf,
> > + const struct btf_type *type,
> > + const char *member_name,
> > + u32 *anon_offset)
> > +{
> > + struct btf_anon_stack *anon_stack;
> > + const struct btf_member *member;
> > + u32 tid, cur_offset = 0;
> > + const char *name;
> > + int i, top = 0;
> > +
> > + anon_stack = kcalloc(BTF_ANON_STACK_MAX, sizeof(*anon_stack), GFP_KERNEL);
> > + if (!anon_stack)
> > + return ERR_PTR(-ENOMEM);
> > +
> > +retry:
> > + if (!btf_type_is_struct(type)) {
> > + member = ERR_PTR(-EINVAL);
> > + goto out;
> > + }
> > +
> > + for_each_member(i, type, member) {
> > + if (!member->name_off) {
> > + /* Anonymous union/struct: push it for later use */
> > + type = btf_type_skip_modifiers(btf, member->type, &tid);
> > + if (type && top < BTF_ANON_STACK_MAX) {
> > + anon_stack[top].tid = tid;
> > + anon_stack[top++].offset =
> > + cur_offset + member->offset;
> > + }
> > + } else {
> > + name = btf_name_by_offset(btf, member->name_off);
> > + if (name && !strcmp(member_name, name)) {
> > + if (anon_offset)
> > + *anon_offset = cur_offset;
> > + goto out;
> > + }
> > + }
> > + }
> > + if (top > 0) {
> > + /* Pop from the anonymous stack and retry */
> > + tid = anon_stack[--top].tid;
> > + cur_offset = anon_stack[top].offset;
> > + type = btf_type_by_id(btf, tid);
> > + goto retry;
> > + }
> > + member = NULL;
> > +
> > +out:
> > + kfree(anon_stack);
> > + return member;
> > +}
> > +
> > #define BTF_SHOW_MAX_ITER 10
> >
> > #define BTF_KIND_BIT(kind) (1ULL << kind)
> > diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
> > index 057cd975d014..64b61f67a403 100644
> > --- a/kernel/trace/Makefile
> > +++ b/kernel/trace/Makefile
> > @@ -99,7 +99,6 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
> > endif
> > obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
> > obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
> > -obj-$(CONFIG_PROBE_EVENTS_BTF_ARGS) += trace_btf.o
> > obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
> > obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
> > obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
> > diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c
> > deleted file mode 100644
> > index ca224d53bfdc..000000000000
> > --- a/kernel/trace/trace_btf.c
> > +++ /dev/null
> > @@ -1,122 +0,0 @@
> > -// SPDX-License-Identifier: GPL-2.0
> > -#include <linux/btf.h>
> > -#include <linux/kernel.h>
> > -#include <linux/slab.h>
> > -
> > -#include "trace_btf.h"
> > -
> > -/*
> > - * Find a function proto type by name, and return the btf_type with its btf
> > - * in *@btf_p. Return NULL if not found.
> > - * Note that caller has to call btf_put(*@btf_p) after using the btf_type.
> > - */
> > -const struct btf_type *btf_find_func_proto(const char *func_name, struct btf **btf_p)
> > -{
> > - const struct btf_type *t;
> > - s32 id;
> > -
> > - id = bpf_find_btf_id(func_name, BTF_KIND_FUNC, btf_p);
> > - if (id < 0)
> > - return NULL;
> > -
> > - /* Get BTF_KIND_FUNC type */
> > - t = btf_type_by_id(*btf_p, id);
> > - if (!t || !btf_type_is_func(t))
> > - goto err;
> > -
> > - /* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
> > - t = btf_type_by_id(*btf_p, t->type);
> > - if (!t || !btf_type_is_func_proto(t))
> > - goto err;
> > -
> > - return t;
> > -err:
> > - btf_put(*btf_p);
> > - return NULL;
> > -}
> > -
> > -/*
> > - * Get function parameter with the number of parameters.
> > - * This can return NULL if the function has no parameters.
> > - * It can return -EINVAL if the @func_proto is not a function proto type.
> > - */
> > -const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, s32 *nr)
> > -{
> > - if (!btf_type_is_func_proto(func_proto))
> > - return ERR_PTR(-EINVAL);
> > -
> > - *nr = btf_type_vlen(func_proto);
> > - if (*nr > 0)
> > - return (const struct btf_param *)(func_proto + 1);
> > - else
> > - return NULL;
> > -}
> > -
> > -#define BTF_ANON_STACK_MAX 16
> > -
> > -struct btf_anon_stack {
> > - u32 tid;
> > - u32 offset;
> > -};
> > -
> > -/*
> > - * Find a member of data structure/union by name and return it.
> > - * Return NULL if not found, or -EINVAL if parameter is invalid.
> > - * If the member is an member of anonymous union/structure, the offset
> > - * of that anonymous union/structure is stored into @anon_offset. Caller
> > - * can calculate the correct offset from the root data structure by
> > - * adding anon_offset to the member's offset.
> > - */
> > -const struct btf_member *btf_find_struct_member(struct btf *btf,
> > - const struct btf_type *type,
> > - const char *member_name,
> > - u32 *anon_offset)
> > -{
> > - struct btf_anon_stack *anon_stack;
> > - const struct btf_member *member;
> > - u32 tid, cur_offset = 0;
> > - const char *name;
> > - int i, top = 0;
> > -
> > - anon_stack = kcalloc(BTF_ANON_STACK_MAX, sizeof(*anon_stack), GFP_KERNEL);
> > - if (!anon_stack)
> > - return ERR_PTR(-ENOMEM);
> > -
> > -retry:
> > - if (!btf_type_is_struct(type)) {
> > - member = ERR_PTR(-EINVAL);
> > - goto out;
> > - }
> > -
> > - for_each_member(i, type, member) {
> > - if (!member->name_off) {
> > - /* Anonymous union/struct: push it for later use */
> > - type = btf_type_skip_modifiers(btf, member->type, &tid);
> > - if (type && top < BTF_ANON_STACK_MAX) {
> > - anon_stack[top].tid = tid;
> > - anon_stack[top++].offset =
> > - cur_offset + member->offset;
> > - }
> > - } else {
> > - name = btf_name_by_offset(btf, member->name_off);
> > - if (name && !strcmp(member_name, name)) {
> > - if (anon_offset)
> > - *anon_offset = cur_offset;
> > - goto out;
> > - }
> > - }
> > - }
> > - if (top > 0) {
> > - /* Pop from the anonymous stack and retry */
> > - tid = anon_stack[--top].tid;
> > - cur_offset = anon_stack[top].offset;
> > - type = btf_type_by_id(btf, tid);
> > - goto retry;
> > - }
> > - member = NULL;
> > -
> > -out:
> > - kfree(anon_stack);
> > - return member;
> > -}
> > -
> > diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h
> > deleted file mode 100644
> > index 4bc44bc261e6..000000000000
> > --- a/kernel/trace/trace_btf.h
> > +++ /dev/null
> > @@ -1,11 +0,0 @@
> > -/* SPDX-License-Identifier: GPL-2.0 */
> > -#include <linux/btf.h>
> > -
> > -const struct btf_type *btf_find_func_proto(const char *func_name,
> > - struct btf **btf_p);
> > -const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
> > - s32 *nr);
> > -const struct btf_member *btf_find_struct_member(struct btf *btf,
> > - const struct btf_type *type,
> > - const char *member_name,
> > - u32 *anon_offset);
> > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > index 4dc74d73fc1d..b33c424b8ee0 100644
> > --- a/kernel/trace/trace_probe.c
> > +++ b/kernel/trace/trace_probe.c
> > @@ -12,7 +12,7 @@
> > #define pr_fmt(fmt) "trace_probe: " fmt
> >
> > #include <linux/bpf.h>
> > -#include "trace_btf.h"
> > +#include <linux/btf.h>
> >
> > #include "trace_probe.h"
> >
>
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH v7 10/10] selftests: ftrace: Add wprobe trigger testcase
From: Masami Hiramatsu (Google) @ 2026-07-15 1:45 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178407983818.95826.12714571928538799781.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a testcase for checking wprobe trigger. This sets set_wprobe and
clear_wprobe triggers on fprobe events to watch dentry access.
So this depends on both wprobe and fprobe.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v7:
- Add a newline at the end of file.(style fix)
- Use dentry_kill instead of __dentry_kill.
Changes in v5:
- Enable CONFIG_WPROBE_TRIGGERS in the config for ftrace test.
Changes in v3:
- Newly added.
---
tools/testing/selftests/ftrace/config | 1
.../ftrace/test.d/trigger/trigger-wprobe.tc | 48 ++++++++++++++++++++
2 files changed, 49 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftests/ftrace/config
index d2f503722020..ecdee77f360f 100644
--- a/tools/testing/selftests/ftrace/config
+++ b/tools/testing/selftests/ftrace/config
@@ -28,3 +28,4 @@ CONFIG_TRACER_SNAPSHOT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
CONFIG_WPROBE_EVENTS=y
+CONFIG_WPROBE_TRIGGERS=y
diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
new file mode 100644
index 000000000000..cc7a0532d7ff
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
@@ -0,0 +1,48 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: event trigger - test wprobe trigger
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README events/sched/sched_process_fork/trigger
+
+echo 0 > tracing_on
+
+:;: "Add a wprobe event used by trigger" ;:
+echo 'w:watch rw@0:8 address=$addr value=+0($addr)' > dynamic_events
+
+:;: "Add events for triggering wprobe" ;:
+echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events
+echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
+
+:;: "Add wprobe triggers" ;:
+echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
+cat events/fprobes/truncate/trigger | grep ^set_wprobe
+cat events/fprobes/dentry_kill/trigger | grep ^clear_wprobe
+
+:;: "Ensure wprobe is still disabled" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Enable events for triggers" ;:
+echo 1 >> events/fprobes/truncate/enable
+echo 1 >> events/fprobes/dentry_kill/enable
+
+:;: "Start test workload" ;:
+echo 1 >> tracing_on
+
+echo aaa > /tmp/hoge
+echo bbb > /tmp/hoge
+echo ccc > /tmp/hoge
+rm /tmp/hoge
+
+:;: "Check trace results" ;:
+cat trace | grep watch
+
+:;: "Ensure wprobe becomes disabled again" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Remove wprobe triggers" ;:
+echo '!set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo '!clear_wprobe:watch' >> events/fprobes/dentry_kill/trigger
+! grep ^set_wprobe events/fprobes/truncate/trigger
+! grep ^clear_wprobe events/fprobes/dentry_kill/trigger
+
+exit 0
^ permalink raw reply related
* [PATCH v7 09/10] tracing: wprobe: Add wprobe event trigger
From: Masami Hiramatsu (Google) @ 2026-07-15 1:45 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178407983818.95826.12714571928538799781.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add wprobe event trigger to set and clear the watch event dynamically.
This allows us to set an watchpoint on a given local variables and
a slab object instead of static objects.
The trigger syntax is below:
- set_wprobe:WPROBE:FIELD[+OFFSET] [if FILTER]
- clear_wprobe:WPROBE[:FIELD[+OFFSET]] [if FILTER]
set_wprobe sets the address pointed by FIELD[+offset] to the WPROBE
event. The FIELD is the field name of trigger event.
clear_wprobe clears the watch address of WPROBE event. If the FIELD
option is specified, it clears only if the current watch address is
same as the given FIELD[+OFFSET] value.
The set_wprobe trigger does not change the type and length, these
must be set when creating a new wprobe.
Also, the WPROBE event must be disabled when setting the new trigger
and it will be busy afterwards. Recommended usage is to add a new
wprobe at NULL address and keep disabled.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v7:
- Use kzalloc_obj().
- Update sample code in document.
Changes in v6:
- Update according to the latest change of trigger ops.
Changes in v5:
- Following the suggestions, the documentation was revised to suit rst.
Changes in v3:
- Add FIELD option support for clear_wprobe and update document.
- Fix to unregister/free event_trigger_data on file correctly.
- Fix syntax comments.
Changes in v2:
- Getting local cpu perf_event from trace_wprobe directly.
- Remove trace_wprobe_local_perf() because it is conditionally unused.
- Make CONFIG_WPROBE_TRIGGERS a hidden config.
---
Documentation/trace/wprobetrace.rst | 89 ++++++++
include/linux/trace_events.h | 1
kernel/trace/Kconfig | 10 +
kernel/trace/trace_wprobe.c | 415 +++++++++++++++++++++++++++++++++++
4 files changed, 515 insertions(+)
diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wprobetrace.rst
index 025b4c39b809..6f3d2afb35e3 100644
--- a/Documentation/trace/wprobetrace.rst
+++ b/Documentation/trace/wprobetrace.rst
@@ -67,3 +67,92 @@ Here is an example to add a wprobe event on a variable `jiffies`.
<idle>-0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130)
You can see the code which writes to `jiffies` is `tick_do_update_jiffies64()`.
+
+Combination with trigger action
+-------------------------------
+The event trigger action can extend the utilization of this wprobe.
+
+- set_wprobe:WPEVENT:FIELD[+|-ADJUST]
+- clear_wprobe:WPEVENT[:FIELD[+|-]ADJUST]
+
+Set these triggers to the target event, then the WPROBE event will be
+setup to trace the memory access at FIELD[+|-ADJUST] address.
+When clear_wprobe is hit, if FIELD is NOT specified, the WPEVENT is
+forcibly cleared. If FIELD[[+|-]ADJUST] is set, it clears WPEVENT only
+if its watching address is the same as the FIELD[[+|-]ADJUST] value.
+
+Notes:
+The set_wprobe trigger does not change the type and length, these
+must be set when creating a new wprobe.
+
+The WPROBE event must be disabled when setting the new trigger
+and it will be busy afterwards. Recommended usage is to add a new
+wprobe at NULL address and keep disabled.
+
+
+For example, trace the first 8 bytes of the dentry data structure passed
+to do_truncate() until it is deleted by dentry_kill().
+(Note: all tracefs setup uses '>>' so that it does not kick do_truncate())
+::
+
+ # echo 'w:watch rw@0:8 address=$addr value=+0($addr)' >> dynamic_events
+ # echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events
+ # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+ # echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
+ # echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
+ # echo 1 >> events/fprobes/truncate/enable
+ # echo 1 >> events/fprobes/dentry_kill/enable
+
+ # echo aaa > /tmp/hoge
+ # echo bbb > /tmp/hoge
+ # echo ccc > /tmp/hoge
+ # rm /tmp/hoge
+
+Then, the trace data will show::
+
+ # tracer: nop
+ #
+ # entries-in-buffer/entries-written: 32/32 #P:8
+ #
+ # _-----=> irqs-off/BH-disabled
+ # / _----=> need-resched
+ # | / _---=> hardirq/softirq
+ # || / _--=> preempt-depth
+ # ||| / _-=> migrate-disable
+ # |||| / delay
+ # TASK-PID CPU# ||||| TIMESTAMP FUNCTION
+ # | | | ||||| | |
+ sh-107 [004] ...1. 9.990418: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+ sh-107 [004] ...1. 9.990914: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b3de78
+ sh-107 [004] ...1. 9.993175: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049ddd40
+ sh-107 [004] ..... 9.995198: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 9.995389: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db998
+ sh-107 [004] ..Zff 9.997503: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997509: watch: (path_openat+0x211/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997514: watch: (path_openat+0xa56/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997518: watch: (path_openat+0xae2/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..... 9.997521: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 9.997582: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004808270
+ sh-107 [004] ...1. 9.999365: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db728
+ sh-107 [004] ...1. 9.999388: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b1c000
+ rm-113 [005] ..Zff 10.000965: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000971: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000984: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000988: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001010: watch: (lookup_one_qstr_excl+0x28/0x140) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001014: watch: (lookup_one_qstr_excl+0xd1/0x140) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001018: watch: (may_delete_dentry+0x1c/0x200) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001021: watch: (may_delete_dentry+0x195/0x200) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001031: watch: (vfs_unlink+0x5e/0x260) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] d.Z.. 10.001067: watch: (d_make_discardable+0x1b/0x40) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] d.Z.. 10.001071: watch: (d_make_discardable+0x29/0x40) address=0xffff8880048083a8 value=0x200080
+ rm-113 [005] ...1. 10.001072: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8
+ rm-113 [005] ...1. 10.001218: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 10.001416: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db110
+ sh-107 [004] ...1. 10.001444: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db248
+ sh-107 [004] ...1. 10.001500: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+ sh-107 [004] ...1. 10.002067: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78
+ sh-107 [004] ...1. 10.904920: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78
+ sh-107 [004] ...1. 10.905129: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+
+You can see the watch event is correctly configured on the dentry.
diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h
index d1e5ab71d928..e6f3bbcbb9af 100644
--- a/include/linux/trace_events.h
+++ b/include/linux/trace_events.h
@@ -729,6 +729,7 @@ enum event_trigger_type {
ETT_EVENT_HIST = (1 << 4),
ETT_HIST_ENABLE = (1 << 5),
ETT_EVENT_EPROBE = (1 << 6),
+ ETT_EVENT_WPROBE = (1 << 7),
};
extern int filter_match_preds(struct event_filter *filter, void *rec);
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index d9b6fa5c35d9..af570fa2a882 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -876,6 +876,16 @@ config WPROBE_EVENTS
Those events can be inserted wherever hardware breakpoints can be
set, and record accessed memory address and values.
+config WPROBE_TRIGGERS
+ depends on WPROBE_EVENTS
+ depends on HAVE_REINSTALL_HW_BREAKPOINT
+ bool
+ default y
+ help
+ This adds an event trigger which will set the wprobe on a specific
+ field of an event. This allows user to trace the memory access of
+ an address pointed by the event field.
+
config BPF_EVENTS
depends on BPF_SYSCALL
depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS
diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c
index dd310a87b333..1812b1e74111 100644
--- a/kernel/trace/trace_wprobe.c
+++ b/kernel/trace/trace_wprobe.c
@@ -6,6 +6,8 @@
*/
#define pr_fmt(fmt) "trace_wprobe: " fmt
+#include <linux/atomic.h>
+#include <linux/errno.h>
#include <linux/hw_breakpoint.h>
#include <linux/kallsyms.h>
#include <linux/list.h>
@@ -14,11 +16,14 @@
#include <linux/perf_event.h>
#include <linux/rculist.h>
#include <linux/security.h>
+#include <linux/spinlock.h>
#include <linux/tracepoint.h>
#include <linux/uaccess.h>
+#include <linux/workqueue.h>
#include <asm/ptrace.h>
+#include "trace.h"
#include "trace_dynevent.h"
#include "trace_output.h"
#include "trace_probe.h"
@@ -691,3 +696,413 @@ static __init int init_wprobe_trace(void)
}
fs_initcall(init_wprobe_trace);
+#ifdef CONFIG_WPROBE_TRIGGERS
+
+static int wprobe_trigger_global_enabled;
+
+#define SET_WPROBE_STR "set_wprobe"
+#define CLEAR_WPROBE_STR "clear_wprobe"
+#define WPROBE_DEFAULT_CLEAR_ADDRESS ((unsigned long)&wprobe_trigger_global_enabled)
+
+struct wprobe_trigger_data {
+ struct trace_event_file *file;
+ struct trace_wprobe *tw;
+
+ struct perf_event_attr attr;
+ raw_spinlock_t lock; /* lock protects attr */
+ struct work_struct work;// TBD: use work + IPI or use sched/raw_syscall event?
+ unsigned int offset;
+ long adjust;
+ const char *field;
+ // size must be unsigned long because it should be an address.
+ bool clear;
+};
+
+static int trace_wprobe_update_local(struct trace_wprobe *tw,
+ struct perf_event_attr *attr)
+{
+ struct perf_event *bp = *this_cpu_ptr(tw->bp_event);
+
+ return modify_wide_hw_breakpoint_local(bp, attr);
+}
+
+static void wprobe_smp_update_func(void *data)
+{
+ struct wprobe_trigger_data *trigger_data = data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&trigger_data->lock, flags);
+ trace_wprobe_update_local(trigger_data->tw, &trigger_data->attr);
+ raw_spin_unlock_irqrestore(&trigger_data->lock, flags);
+}
+
+static void wprobe_work_func(struct work_struct *work)
+{
+ struct wprobe_trigger_data *data = container_of(work, struct wprobe_trigger_data, work);
+
+ on_each_cpu(wprobe_smp_update_func, data, false);
+}
+
+static void wprobe_trigger(struct event_trigger_data *data,
+ struct trace_buffer *buffer, void *rec,
+ struct ring_buffer_event *event)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+ struct perf_event_attr *attr = &wprobe_data->attr;
+ struct trace_wprobe *tw = wprobe_data->tw;
+ unsigned long addr, flags;
+ int ret = -EBUSY;
+
+ addr = *(unsigned long *)(rec + wprobe_data->offset);
+ addr += wprobe_data->adjust;
+
+ raw_spin_lock_irqsave(&wprobe_data->lock, flags);
+
+ if (!wprobe_data->clear) {
+ if (tw->addr != WPROBE_DEFAULT_CLEAR_ADDRESS)
+ goto unlock;
+
+ tw->addr = attr->bp_addr = addr;
+ ret = trace_wprobe_update_local(tw, attr);
+ if (WARN_ON_ONCE(ret))
+ goto unlock;
+ clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags);
+ } else {
+ if (tw->addr == WPROBE_DEFAULT_CLEAR_ADDRESS)
+ goto unlock;
+ if (wprobe_data->field && tw->addr != addr)
+ goto unlock;
+
+ tw->addr = attr->bp_addr = WPROBE_DEFAULT_CLEAR_ADDRESS;
+ ret = trace_wprobe_update_local(tw, attr);
+ if (WARN_ON_ONCE(ret))
+ goto unlock;
+ set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags);
+ }
+ schedule_work(&wprobe_data->work);
+unlock:
+ raw_spin_unlock_irqrestore(&wprobe_data->lock, flags);
+}
+
+static void free_wprobe_trigger_data(struct wprobe_trigger_data *wprobe_data)
+{
+ if (wprobe_data)
+ kfree(wprobe_data->field);
+ kfree(wprobe_data);
+}
+
+DEFINE_FREE(free_wprobe_trigger_data, struct wprobe_trigger_data *, free_wprobe_trigger_data(_T));
+
+static int wprobe_trigger_print(struct seq_file *m,
+ struct event_trigger_data *data)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+
+ if (wprobe_data->clear) {
+ seq_printf(m, "%s:%s", CLEAR_WPROBE_STR,
+ trace_event_name(wprobe_data->file->event_call));
+ if (wprobe_data->field) {
+ seq_printf(m, ":%s%+ld",
+ wprobe_data->field, wprobe_data->adjust);
+ }
+ } else
+ seq_printf(m, "%s:%s:%s%+ld", SET_WPROBE_STR,
+ trace_event_name(wprobe_data->file->event_call),
+ wprobe_data->field, wprobe_data->adjust);
+
+ if (data->filter_str)
+ seq_printf(m, " if %s\n", data->filter_str);
+ else
+ seq_putc(m, '\n');
+
+ return 0;
+}
+
+static struct wprobe_trigger_data *
+wprobe_trigger_alloc(struct trace_wprobe *tw, struct trace_event_file *file,
+ bool clear)
+{
+ struct wprobe_trigger_data *wprobe_data;
+ struct perf_event_attr *attr;
+
+ wprobe_data = kzalloc_obj(*wprobe_data);
+ if (!wprobe_data)
+ return NULL;
+
+ wprobe_data->tw = tw;
+ wprobe_data->clear = clear;
+ wprobe_data->file = file;
+
+ attr = &wprobe_data->attr;
+ hw_breakpoint_init(attr);
+ attr->bp_type = tw->type;
+ attr->bp_addr = WPROBE_DEFAULT_CLEAR_ADDRESS;
+ attr->bp_len = tw->len;
+
+ raw_spin_lock_init(&wprobe_data->lock);
+ INIT_WORK(&wprobe_data->work, wprobe_work_func);
+
+ return wprobe_data;
+}
+
+static void wprobe_trigger_free(struct event_trigger_data *data)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+
+ if (WARN_ON_ONCE(data->ref <= 0))
+ return;
+
+ data->ref--;
+ if (!data->ref) {
+ /* Remove the SOFT_MODE flag */
+ trace_event_enable_disable(wprobe_data->file, 0, 1);
+ trace_event_put_ref(wprobe_data->file->event_call);
+ trigger_data_free(data);
+ free_wprobe_trigger_data(wprobe_data);
+ }
+}
+
+static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops,
+ struct trace_event_file *file,
+ char *glob, char *cmd,
+ char *param_and_filter)
+{
+ /*
+ * set_wprobe:EVENT:FIELD[+OFFS]
+ * clear_wprobe:EVENT[:FIELD[+OFFS]]
+ */
+ struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = NULL;
+ struct event_trigger_data *trigger_data __free(kfree) = NULL;
+ struct ftrace_event_field *field = NULL;
+ struct trace_event_file *wprobe_file;
+ struct trace_array *tr = file->tr;
+ struct trace_event_call *event;
+ struct perf_event_attr *attr;
+ char *event_str, *field_str;
+ bool remove, clear = false;
+ struct trace_wprobe *tw;
+ char *param, *filter;
+ int ret;
+
+ remove = event_trigger_check_remove(glob);
+
+ if (!strcmp(cmd, CLEAR_WPROBE_STR))
+ clear = true;
+
+ if (event_trigger_empty_param(param_and_filter))
+ return -EINVAL;
+
+ ret = event_trigger_separate_filter(param_and_filter, ¶m, &filter, true);
+ if (ret)
+ return ret;
+
+ event_str = strsep(¶m, ":");
+
+ /* Find target wprobe */
+ tw = find_trace_wprobe(event_str, WPROBE_EVENT_SYSTEM);
+ if (!tw)
+ return -ENOENT;
+ /* The target wprobe must not be used (unless clear) */
+ if (!remove && !clear && trace_probe_is_enabled(&tw->tp))
+ return -EBUSY;
+
+ wprobe_file = find_event_file(tr, WPROBE_EVENT_SYSTEM, event_str);
+ if (!wprobe_file)
+ return -EINVAL;
+
+ wprobe_data = wprobe_trigger_alloc(tw, wprobe_file, clear);
+ if (!wprobe_data)
+ return -ENOMEM;
+ attr = &wprobe_data->attr;
+
+ /* Find target field, which must be equivarent to "void *" */
+ field_str = strsep(¶m, ":");
+ /* trigger removing or clear_wprobe does not need field. */
+ if (!remove && !clear && !field_str)
+ return -EINVAL;
+
+ if (field_str) {
+ char *offs;
+
+ offs = strpbrk(field_str, "+-");
+ if (offs) {
+ long val;
+
+ if (kstrtol(offs, 0, &val) < 0)
+ return -EINVAL;
+ wprobe_data->adjust = val;
+ *offs = '\0';
+ }
+
+ event = file->event_call;
+ field = trace_find_event_field(event, field_str);
+ if (!field)
+ return -ENOENT;
+
+ if (field->size != sizeof(void *))
+ return -ENOEXEC;
+ wprobe_data->offset = field->offset;
+ wprobe_data->field = kstrdup(field_str, GFP_KERNEL);
+ if (!wprobe_data->field)
+ return -ENOMEM;
+ }
+
+ trigger_data = trigger_data_alloc(cmd_ops, cmd, param, wprobe_data);
+ if (!trigger_data)
+ return -ENOMEM;
+
+ /* Up the trigger_data count to make sure nothing frees it on failure */
+ event_trigger_init(trigger_data);
+
+ if (remove) {
+ event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
+ return 0;
+ }
+
+ ret = event_trigger_parse_num(param, trigger_data);
+ if (ret)
+ return ret;
+
+ ret = event_trigger_set_filter(cmd_ops, file, filter, trigger_data);
+ if (ret < 0)
+ return ret;
+
+ /* Soft-enable (register) wprobe event on WPROBE_DEFAULT_CLEAR_ADDRESS */
+ tw->addr = attr->bp_addr = WPROBE_DEFAULT_CLEAR_ADDRESS;
+ ret = trace_event_enable_disable(wprobe_file, 1, 1);
+ if (ret < 0) {
+ event_trigger_reset_filter(cmd_ops, trigger_data);
+ return ret;
+ }
+ ret = event_trigger_register(cmd_ops, file, glob, trigger_data);
+ if (ret) {
+ event_trigger_reset_filter(cmd_ops, trigger_data);
+ trace_event_enable_disable(wprobe_file, 0, 1);
+ return ret;
+ }
+ /* Make it NULL to avoid freeing trigger_data and wprobe_data by __free() */
+ trigger_data = NULL;
+ wprobe_data = NULL;
+
+ return 0;
+}
+
+/* Return event_trigger_data if there is a trigger which points the same wprobe */
+static struct event_trigger_data *
+wprobe_trigger_find_same(struct event_trigger_data *test,
+ struct trace_event_file *file)
+{
+ struct wprobe_trigger_data *test_wprobe_data = test->private_data;
+ struct wprobe_trigger_data *wprobe_data;
+ struct event_trigger_data *iter;
+
+ list_for_each_entry(iter, &file->triggers, list) {
+ wprobe_data = iter->private_data;
+ if (!wprobe_data ||
+ iter->cmd_ops->trigger_type !=
+ test->cmd_ops->trigger_type)
+ continue;
+ if (wprobe_data->tw == test_wprobe_data->tw)
+ return iter;
+ }
+ return NULL;
+}
+
+static int wprobe_register_trigger(char *glob,
+ struct event_trigger_data *data,
+ struct trace_event_file *file)
+{
+ int ret = 0;
+
+ lockdep_assert_held(&event_mutex);
+
+ /* The same wprobe is not accept on the same file (event) */
+ if (wprobe_trigger_find_same(data, file))
+ return -EEXIST;
+
+ if (data->cmd_ops->init) {
+ ret = data->cmd_ops->init(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ list_add_rcu(&data->list, &file->triggers);
+
+ update_cond_flag(file);
+ ret = trace_event_trigger_enable_disable(file, 1);
+ if (ret < 0) {
+ list_del_rcu(&data->list);
+ update_cond_flag(file);
+ }
+ return ret;
+}
+
+static void wprobe_unregister_trigger(char *glob,
+ struct event_trigger_data *test,
+ struct trace_event_file *file)
+{
+ struct event_trigger_data *data;
+
+ lockdep_assert_held(&event_mutex);
+
+ data = wprobe_trigger_find_same(test, file);
+ if (!data)
+ return;
+
+ list_del_rcu(&data->list);
+ trace_event_trigger_enable_disable(file, 0);
+ update_cond_flag(file);
+ if (data->cmd_ops->free)
+ data->cmd_ops->free(data);
+}
+
+static struct event_command trigger_wprobe_set_cmd = {
+ .name = SET_WPROBE_STR,
+ .trigger_type = ETT_EVENT_WPROBE,
+ /* This triggers after when the event is recorded. */
+ .flags = EVENT_CMD_FL_NEEDS_REC,
+ .parse = wprobe_trigger_cmd_parse,
+ .reg = wprobe_register_trigger,
+ .unreg = wprobe_unregister_trigger,
+ .set_filter = set_trigger_filter,
+ .trigger = wprobe_trigger,
+ .count_func = event_trigger_count,
+ .print = wprobe_trigger_print,
+ .init = event_trigger_init,
+ .free = wprobe_trigger_free,
+};
+
+static struct event_command trigger_wprobe_clear_cmd = {
+ .name = CLEAR_WPROBE_STR,
+ .trigger_type = ETT_EVENT_WPROBE,
+ /* This triggers after when the event is recorded. */
+ .flags = EVENT_CMD_FL_NEEDS_REC,
+ .parse = wprobe_trigger_cmd_parse,
+ .reg = wprobe_register_trigger,
+ .unreg = wprobe_unregister_trigger,
+ .set_filter = set_trigger_filter,
+ .trigger = wprobe_trigger,
+ .count_func = event_trigger_count,
+ .print = wprobe_trigger_print,
+ .init = event_trigger_init,
+ .free = wprobe_trigger_free,
+};
+
+static __init int init_trigger_wprobe_cmds(void)
+{
+ int ret;
+
+ ret = register_event_command(&trigger_wprobe_set_cmd);
+ if (WARN_ON(ret < 0))
+ return ret;
+ ret = register_event_command(&trigger_wprobe_clear_cmd);
+ if (WARN_ON(ret < 0))
+ unregister_event_command(&trigger_wprobe_set_cmd);
+
+ if (!ret)
+ wprobe_trigger_global_enabled = 1;
+
+ return ret;
+}
+fs_initcall(init_trigger_wprobe_cmds);
+#endif /* CONFIG_WPROBE_TRIGGERS */
^ permalink raw reply related
* [PATCH v7 08/10] HWBP: Add modify_wide_hw_breakpoint_local() API
From: Masami Hiramatsu (Google) @ 2026-07-15 1:45 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178407983818.95826.12714571928538799781.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add modify_wide_hw_breakpoint_local() arch-wide interface which allows
hwbp users to update watch address on-line. This is available if the
arch supports CONFIG_HAVE_REINSTALL_HW_BREAKPOINT.
Note that this allows to change the type only for compatible types,
because it does not release and reserve the hwbp slot based on type.
For instance, you can not change HW_BREAKPOINT_W to HW_BREAKPOINT_X.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v7:
- Update bp->attr.bp_attr so that we can correctly check the
address on it.
- Use -EOPNOTSUPP instead of -ENOSYS.
Changes in v4:
- Update kerneldoc comment about modify_wide_hw_breakpoint_local
according to Randy's comment.
Changes in v2:
- Check type compatibility by checking slot. (Thanks Jinchao!)
---
arch/Kconfig | 10 ++++++++++
arch/x86/Kconfig | 1 +
include/linux/hw_breakpoint.h | 6 ++++++
kernel/events/hw_breakpoint.c | 39 +++++++++++++++++++++++++++++++++++++++
4 files changed, 56 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index 959aee9568ff..4a87e843bb4c 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -467,6 +467,16 @@ config HAVE_POST_BREAKPOINT_HOOK
Select this option if your arch implements breakpoints overflow
handler hooks after the target memory is modified.
+config HAVE_REINSTALL_HW_BREAKPOINT
+ bool
+ depends on HAVE_HW_BREAKPOINT
+ help
+ Depending on the arch implementation of hardware breakpoints,
+ some of them are able to update the breakpoint configuration
+ without release and reserve the hardware breakpoint register.
+ What configuration is able to update depends on hardware and
+ software implementation.
+
config HAVE_USER_RETURN_NOTIFIER
bool
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 6b7e14ef8cfb..588218da8f41 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -247,6 +247,7 @@ config X86
select HAVE_GCC_PLUGINS
select HAVE_HW_BREAKPOINT
select HAVE_POST_BREAKPOINT_HOOK
+ select HAVE_REINSTALL_HW_BREAKPOINT
select HAVE_IOREMAP_PROT
select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64
select HAVE_IRQ_TIME_ACCOUNTING
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
index db199d653dd1..6754ffbee9ed 100644
--- a/include/linux/hw_breakpoint.h
+++ b/include/linux/hw_breakpoint.h
@@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
perf_overflow_handler_t triggered,
void *context);
+extern int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr);
+
extern int register_perf_hw_breakpoint(struct perf_event *bp);
extern void unregister_hw_breakpoint(struct perf_event *bp);
extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events);
@@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
perf_overflow_handler_t triggered,
void *context) { return NULL; }
static inline int
+modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr) { return -EOPNOTSUPP; }
+static inline int
register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; }
static inline void unregister_hw_breakpoint(struct perf_event *bp) { }
static inline void
diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c
index 789add0c185a..4337688da397 100644
--- a/kernel/events/hw_breakpoint.c
+++ b/kernel/events/hw_breakpoint.c
@@ -888,6 +888,45 @@ void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events)
}
EXPORT_SYMBOL_GPL(unregister_wide_hw_breakpoint);
+/**
+ * modify_wide_hw_breakpoint_local - update breakpoint config for local CPU
+ * @bp: the hwbp perf event for this CPU
+ * @attr: the new attribute for @bp
+ *
+ * This does not release and reserve the slot of a HWBP; it just reuses the
+ * current slot on local CPU. So the users must update the other CPUs by
+ * themselves.
+ * Also, since this does not release/reserve the slot, this can not change the
+ * type to incompatible type of the HWBP.
+ * Return err if attr is invalid or the CPU fails to update debug register
+ * for new @attr.
+ */
+#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr)
+{
+ int ret;
+
+ if (find_slot_idx(bp->attr.bp_type) != find_slot_idx(attr->bp_type))
+ return -EINVAL;
+
+ ret = hw_breakpoint_arch_parse(bp, attr, counter_arch_bp(bp));
+ if (ret)
+ return ret;
+
+ bp->attr.bp_addr = attr->bp_addr;
+
+ return arch_reinstall_hw_breakpoint(bp);
+}
+#else
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+EXPORT_SYMBOL_GPL(modify_wide_hw_breakpoint_local);
+
/**
* hw_breakpoint_is_used - check if breakpoints are currently used
*
^ 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