From: Roman 'Hedin' Storozhenko <romeusmeister@gmail.com>
To: Paul Walmsley <pjw@kernel.org>,
Palmer Dabbelt <palmer@dabbelt.com>,
Albert Ou <aou@eecs.berkeley.edu>,
Alexandre Ghiti <alex@ghiti.fr>,
Steven Rostedt <rostedt@goodmis.org>,
Masami Hiramatsu <mhiramat@kernel.org>,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: "Jisheng Zhang" <jszhang@kernel.org>,
"Radim Krčmář" <rkrcmar@ventanamicro.com>,
linux-riscv@lists.infradead.org, linux-kernel@vger.kernel.org,
linux-trace-kernel@vger.kernel.org,
"Roman 'Hedin' Storozhenko" <romeusmeister@gmail.com>
Subject: [PATCH v3] riscv: mm: Trace TLB flush requests
Date: Thu, 15 Oct 2026 19:03:20 +0200 [thread overview]
Message-ID: <20261015-tlb_tracepoint-v3-1-eef305a3bb63@gmail.com> (raw)
Make RISC-V TLB flush requests observable together with the
architecture-specific context available when the request reaches the
RISC-V implementation.
The generic tlb:tlb_flush event describes TLB flush activity using
architecture-independent reason and page-count information. At the
RISC-V implementation level, additional request context is available,
including the invalidation range and stride, ASID, target CPU mask, and
the requested invalidation scope.
This information is useful for debugging and workload analysis. The
target CPU mask can show the address-space CPU footprint involved in an
invalidation, while the range and stride distinguish requests operating
on different mapping granularities. Recording the ASID and requested
scope makes it possible to correlate those requests with other MM,
scheduler, IPI, and firmware activity.
Add riscv_tlb:riscv_tlb_flush_request in flush_tlb_all() and
__flush_tlb_range(). Record start, size, stride, the hardware-visible
ASID, whether a specific mm is associated with the request, the target
CPU mask and its weight, and the requested scope.
Record the complete target mask in addition to its weight because CPU
identity cannot be reconstructed from a count and is useful when
correlating TLB invalidations with per-CPU activity.
The event describes the semantic invalidation request before it is
dispatched. It intentionally does not record whether a remote request is
implemented through SBI RFENCE or Linux cross-CPU coordination, since
that choice is effectively established during system initialization
rather than selected independently for each request.
Tested on QEMU virt with OpenSBI using local and shared-mm
mprotect()/munmap() workloads. These exercised single, range, and
address-space invalidation requests with different target CPU masks.
Also tested on QEMU virt with APLIC+IMSIC. A MADV_PAGEOUT reclaim
workload was used to exercise mm-independent global flushes. All scope
values (single, range, address-space, and all) were observed.
Signed-off-by: Roman 'Hedin' Storozhenko <romeusmeister@gmail.com>
---
Add a RISC-V tracepoint for observing TLB invalidation requests as they
reach the architecture-specific implementation.
The generic tlb:tlb_flush event describes TLB flush activity using
architecture-independent reason and page-count information. At the
RISC-V implementation level, additional request context is available,
including the invalidation range and stride, ASID, target CPU mask, and
derived request scope.
The tracepoint is intended to make RISC-V TLB invalidation activity
easier to correlate with MM, scheduler, IPI, and firmware activity. For
mm-associated invalidations, the target CPU mask can show the
address-space CPU footprint involved in the request, while the range and
stride distinguish requests operating on different mapping
granularities.
The event describes the semantic invalidation request before it is
dispatched. It intentionally does not report whether a remote request is
implemented through SBI RFENCE or Linux cross-CPU coordination, since
that choice is effectively established during system initialization
rather than selected independently for each request.
The patch was tested on QEMU virt with OpenSBI using local and shared-mm
mprotect()/munmap() workloads. These exercised single, range, and
address-space invalidation requests with different target CPU masks.
It was also tested on QEMU virt with APLIC+IMSIC. A MADV_PAGEOUT reclaim
workload was used to exercise mm-independent global flushes. All derived
request scope values -- single, range, address-space, and all -- were
observed.
---
Changes in v3:
- Rebase on current RISC-V for-next.
- Thanks to Jisheng Zhang for pointing out that SBI RFENCE versus Linux
cross-CPU coordination is effectively a system-wide choice established
during initialization rather than a per-request decision.
- Drop the local/SBI RFENCE/cross-CPU path field.
- Rename the event from riscv_tlb_flush_path to
riscv_tlb_flush_request and refocus it on the semantic TLB
invalidation request.
- Emit one request event independently of the mechanism subsequently
used to perform the invalidation.
- Address checkpatch.pl --strict alignment issues.
- Link to v2: https://lore.kernel.org/r/20260830-tlb_tracepoint-v2-1-e3c88c24df5b@gmail.com
Changes in v2:
- Use trace_call__riscv_tlb_flush_path() after the explicit
trace_riscv_tlb_flush_path_enabled() check to avoid a second
tracepoint static-key test, as suggested by Steven Rostedt.
- Link to v1: https://lore.kernel.org/r/20260829-tlb_tracepoint-v1-1-dfdaede7e741@gmail.com
---
arch/riscv/mm/tlbflush.c | 54 ++++++++++++++++++++----
include/trace/events/riscv_tlb.h | 90 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 137 insertions(+), 7 deletions(-)
diff --git a/arch/riscv/mm/tlbflush.c b/arch/riscv/mm/tlbflush.c
index 962db300a166..0e7c8088de5e 100644
--- a/arch/riscv/mm/tlbflush.c
+++ b/arch/riscv/mm/tlbflush.c
@@ -9,6 +9,9 @@
#include <asm/mmu_context.h>
#include <asm/cpufeature.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/riscv_tlb.h>
+
#define has_svinval() riscv_has_extension_unlikely(RISCV_ISA_EXT_SVINVAL)
/*
@@ -47,7 +50,9 @@ static void local_flush_tlb_range_threshold_asid(unsigned long start,
}
static inline void local_flush_tlb_range_asid(unsigned long start,
- unsigned long size, unsigned long stride, unsigned long asid)
+ unsigned long size,
+ unsigned long stride,
+ unsigned long asid)
{
if (size <= stride)
local_flush_tlb_page_asid(start, asid);
@@ -63,6 +68,32 @@ void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
local_flush_tlb_range_asid(start, end - start, PAGE_SIZE, FLUSH_TLB_NO_ASID);
}
+static enum riscv_tlb_flush_scope
+riscv_tlb_get_flush_scope(unsigned long size, unsigned long stride, bool has_mm)
+{
+ if (size == FLUSH_TLB_MAX_SIZE)
+ return has_mm ? RISCV_TLB_FLUSH_SCOPE_ADDRESS_SPACE :
+ RISCV_TLB_FLUSH_SCOPE_ALL;
+
+ return size <= stride ? RISCV_TLB_FLUSH_SCOPE_SINGLE :
+ RISCV_TLB_FLUSH_SCOPE_RANGE;
+}
+
+static __always_inline void
+riscv_tlb_trace_flush_request(const struct cpumask *cmask, unsigned long start,
+ unsigned long size, unsigned long stride,
+ unsigned long asid, bool has_mm)
+{
+ enum riscv_tlb_flush_scope scope;
+
+ if (!trace_riscv_tlb_flush_request_enabled())
+ return;
+
+ scope = riscv_tlb_get_flush_scope(size, stride, has_mm);
+ trace_call__riscv_tlb_flush_request(start, size, stride, asid, has_mm,
+ cmask, scope);
+}
+
static void __ipi_flush_tlb_all(void *info)
{
local_flush_tlb_all();
@@ -70,12 +101,18 @@ static void __ipi_flush_tlb_all(void *info)
void flush_tlb_all(void)
{
- if (num_online_cpus() < 2)
+ riscv_tlb_trace_flush_request(cpu_online_mask, 0,
+ FLUSH_TLB_MAX_SIZE, 0,
+ FLUSH_TLB_NO_ASID, false);
+
+ if (num_online_cpus() < 2) {
local_flush_tlb_all();
- else if (riscv_use_sbi_for_rfence())
- sbi_remote_sfence_vma_asid(NULL, 0, FLUSH_TLB_MAX_SIZE, FLUSH_TLB_NO_ASID);
- else
+ } else if (riscv_use_sbi_for_rfence()) {
+ sbi_remote_sfence_vma_asid(NULL, 0, FLUSH_TLB_MAX_SIZE,
+ FLUSH_TLB_NO_ASID);
+ } else {
on_each_cpu(__ipi_flush_tlb_all, NULL, 1);
+ }
}
struct flush_tlb_range_data {
@@ -105,6 +142,8 @@ static void __flush_tlb_range(struct mm_struct *mm,
cpu = get_cpu();
+ riscv_tlb_trace_flush_request(cmask, start, size, stride, asid, !!mm);
+
/* Check if the TLB flush needs to be sent to other CPUs. */
if (cpumask_any_but(cmask, cpu) >= nr_cpu_ids) {
local_flush_tlb_range_asid(start, size, stride, asid);
@@ -185,7 +224,7 @@ void flush_tlb_kernel_range(unsigned long start, unsigned long end)
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
void flush_pmd_tlb_range(struct vm_area_struct *vma, unsigned long start,
- unsigned long end)
+ unsigned long end)
{
__flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),
start, end - start, PMD_SIZE);
@@ -205,7 +244,8 @@ bool arch_tlbbatch_should_defer(struct mm_struct *mm)
}
void arch_tlbbatch_add_pending(struct arch_tlbflush_unmap_batch *batch,
- struct mm_struct *mm, unsigned long start, unsigned long end)
+ struct mm_struct *mm, unsigned long start,
+ unsigned long end)
{
cpumask_or(&batch->cpumask, &batch->cpumask, mm_cpumask(mm));
mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end);
diff --git a/include/trace/events/riscv_tlb.h b/include/trace/events/riscv_tlb.h
new file mode 100644
index 000000000000..f289480a2c0e
--- /dev/null
+++ b/include/trace/events/riscv_tlb.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM riscv_tlb
+
+#if !defined(_TRACE_RISCV_TLB_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_RISCV_TLB_H
+
+#include <linux/cpumask.h>
+#include <linux/tracepoint.h>
+
+#ifndef _TRACE_RISCV_TLB_ENUMS
+#define _TRACE_RISCV_TLB_ENUMS
+
+enum riscv_tlb_flush_scope {
+ RISCV_TLB_FLUSH_SCOPE_SINGLE,
+ RISCV_TLB_FLUSH_SCOPE_RANGE,
+ RISCV_TLB_FLUSH_SCOPE_ADDRESS_SPACE,
+ RISCV_TLB_FLUSH_SCOPE_ALL,
+};
+
+#endif /* _TRACE_RISCV_TLB_ENUMS */
+
+TRACE_DEFINE_ENUM(RISCV_TLB_FLUSH_SCOPE_SINGLE);
+TRACE_DEFINE_ENUM(RISCV_TLB_FLUSH_SCOPE_RANGE);
+TRACE_DEFINE_ENUM(RISCV_TLB_FLUSH_SCOPE_ADDRESS_SPACE);
+TRACE_DEFINE_ENUM(RISCV_TLB_FLUSH_SCOPE_ALL);
+
+#define show_riscv_tlb_flush_scope(scope) \
+ __print_symbolic(scope, \
+ { RISCV_TLB_FLUSH_SCOPE_SINGLE, "single" }, \
+ { RISCV_TLB_FLUSH_SCOPE_RANGE, "range" }, \
+ { RISCV_TLB_FLUSH_SCOPE_ADDRESS_SPACE, "address-space" }, \
+ { RISCV_TLB_FLUSH_SCOPE_ALL, "all" })
+
+/*
+ * Record the invalidation request received by the RISC-V architecture code
+ *
+ * The target CPU mask represents the CPUs Linux intends to cover for the
+ * request. It can be correlated with per-CPU activity, but does not describe
+ * which harts ultimately performed an invalidation.
+ *
+ * The ASID is hardware-visible and may be reused. It must not be treated as a
+ * persistent identifier for an mm.
+ *
+ * The stride describes the invalidation granularity supplied to the RISC-V
+ * implementation.
+ *
+ * The event records the invalidation request before it is dispatched. It does
+ * not describe the mechanism used to execute the request.
+ *
+ */
+TRACE_EVENT(riscv_tlb_flush_request,
+ TP_PROTO(unsigned long start, unsigned long size,
+ unsigned long stride, unsigned long asid, bool has_mm,
+ const struct cpumask *cmask,
+ enum riscv_tlb_flush_scope scope),
+
+ TP_ARGS(start, size, stride, asid, has_mm, cmask, scope),
+
+ TP_STRUCT__entry(__field(unsigned long, start)
+ __field(unsigned long, size)
+ __field(unsigned long, stride)
+ __field(unsigned long, asid)
+ __field(bool, has_mm)
+ __field(unsigned int, target_mask_weight)
+ __cpumask(target_cpus)
+ __field(u8, scope)
+ ),
+
+ TP_fast_assign(__entry->start = start;
+ __entry->size = size;
+ __entry->stride = stride;
+ __entry->asid = asid;
+ __entry->has_mm = has_mm;
+ __entry->target_mask_weight = cpumask_weight(cmask);
+ __assign_cpumask(target_cpus, cpumask_bits(cmask));
+ __entry->scope = scope;
+ ),
+
+ TP_printk("start=%#lx size=%#lx stride=%#lx asid=%#lx has_mm=%d target_mask_weight=%u target_cpus=%s scope=%s",
+ __entry->start, __entry->size, __entry->stride,
+ __entry->asid, __entry->has_mm,
+ __entry->target_mask_weight, __get_cpumask(target_cpus),
+ show_riscv_tlb_flush_scope(__entry->scope))
+);
+
+#endif /* _TRACE_RISCV_TLB_H */
+
+/* This part must be outside protection. */
+#include <trace/define_trace.h>
---
base-commit: a5ae1bf65cee84bcc121d659e7e22a767853f5f5
change-id: 20260829-tlb_tracepoint-844105ab5092
Best regards,
--
Roman 'Hedin' Storozhenko <romeusmeister@gmail.com>
reply other threads:[~2026-10-15 17:03 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261015-tlb_tracepoint-v3-1-eef305a3bb63@gmail.com \
--to=romeusmeister@gmail.com \
--cc=alex@ghiti.fr \
--cc=aou@eecs.berkeley.edu \
--cc=jszhang@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mhiramat@kernel.org \
--cc=palmer@dabbelt.com \
--cc=pjw@kernel.org \
--cc=rkrcmar@ventanamicro.com \
--cc=rostedt@goodmis.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox