* [PATCH v3 1/5] x86/mm: Account for remote kernel TLB flush requests
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
@ 2026-10-12 3:41 ` Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 2/5] x86/mm: Share the full TLB flush dispatch Chuyi Zhou
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chuyi Zhou @ 2026-10-12 3:41 UTC (permalink / raw)
To: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, bigeasy, clrkwllms, rostedt, nadav.amit
Cc: linux-kernel, Chuyi Zhou
NR_TLB_REMOTE_FLUSH counts requests to invalidate TLB entries on other
CPUs. flush_tlb_all() and native_flush_tlb_multi() account for these
requests, but flush_tlb_kernel_range() bypasses both functions and does
not update the counter. Kernel range flushes are therefore missing from
the sender-side statistics, including ranges promoted to a full flush.
Count each request in kernel_tlb_flush_all() and
kernel_tlb_flush_range(). Account for both IPI and INVLPGB backends, as
flush_tlb_all() already does, and count once per request regardless of
the number of target CPUs or invalidation instructions.
Signed-off-by: Chuyi Zhou <zhouchuyi@bytedance.com>
---
arch/x86/mm/tlb.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index 0c4da320831c..da7c408f921c 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1488,6 +1488,8 @@ static void do_kernel_range_flush(void *info)
static void kernel_tlb_flush_all(struct flush_tlb_info *info)
{
+ count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);
+
if (cpu_feature_enabled(X86_FEATURE_INVLPGB))
invlpgb_flush_all();
else
@@ -1496,6 +1498,8 @@ static void kernel_tlb_flush_all(struct flush_tlb_info *info)
static void kernel_tlb_flush_range(struct flush_tlb_info *info)
{
+ count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);
+
if (cpu_feature_enabled(X86_FEATURE_INVLPGB))
invlpgb_kernel_range_flush(info);
else
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 2/5] x86/mm: Share the full TLB flush dispatch
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 1/5] x86/mm: Account for remote kernel TLB flush requests Chuyi Zhou
@ 2026-10-12 3:41 ` Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 3/5] x86/mm: Extract the TLB range flush threshold check Chuyi Zhou
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chuyi Zhou @ 2026-10-12 3:41 UTC (permalink / raw)
To: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, bigeasy, clrkwllms, rostedt, nadav.amit
Cc: linux-kernel, Chuyi Zhou
flush_tlb_all() and kernel_tlb_flush_all() select the same INVLPGB or
IPI backend and account for the same sender-side event. Keeping two
implementations duplicates the backend selection and accounting.
Use flush_tlb_all() for kernel ranges promoted to a full flush and
remove kernel_tlb_flush_all(). Share the existing backend dispatch and
event accounting so each request is counted once.
Signed-off-by: Chuyi Zhou <zhouchuyi@bytedance.com>
---
arch/x86/mm/tlb.c | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index da7c408f921c..5a1b96c61050 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1486,16 +1486,6 @@ static void do_kernel_range_flush(void *info)
flush_tlb_one_kernel(addr);
}
-static void kernel_tlb_flush_all(struct flush_tlb_info *info)
-{
- count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);
-
- if (cpu_feature_enabled(X86_FEATURE_INVLPGB))
- invlpgb_flush_all();
- else
- on_each_cpu(do_flush_tlb_all, NULL, 1);
-}
-
static void kernel_tlb_flush_range(struct flush_tlb_info *info)
{
count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);
@@ -1515,7 +1505,7 @@ void flush_tlb_kernel_range(unsigned long start, unsigned long end)
TLB_GENERATION_INVALID);
if (info.end == TLB_FLUSH_ALL)
- kernel_tlb_flush_all(&info);
+ flush_tlb_all();
else
kernel_tlb_flush_range(&info);
}
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 3/5] x86/mm: Extract the TLB range flush threshold check
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 1/5] x86/mm: Account for remote kernel TLB flush requests Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 2/5] x86/mm: Share the full TLB flush dispatch Chuyi Zhou
@ 2026-10-12 3:41 ` Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 4/5] x86/mm: Initialize kernel TLB flush requests directly Chuyi Zhou
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chuyi Zhou @ 2026-10-12 3:41 UTC (permalink / raw)
To: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, bigeasy, clrkwllms, rostedt, nadav.amit
Cc: linux-kernel, Chuyi Zhou
The decision to replace a range flush with a full flush is embedded in
init_flush_tlb_info(). Both mm and kernel flushes use this policy, but
the kernel path only needs the range and the decision, without the
mm-specific descriptor initialization.
Extract the threshold predicate into tlb_range_exceeds_ceiling()
and use it in init_flush_tlb_info(). Preserve the unsigned range
arithmetic, right-shift rounding and strict greater-than comparison.
Descriptor initialization and both flush paths remain unchanged.
Signed-off-by: Chuyi Zhou <zhouchuyi@bytedance.com>
---
arch/x86/mm/tlb.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index 5a1b96c61050..0d03011c6825 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1373,6 +1373,12 @@ void flush_tlb_multi(const struct cpumask *cpumask,
*/
unsigned long tlb_single_page_flush_ceiling __read_mostly = 33;
+static bool tlb_range_exceeds_ceiling(unsigned long start, unsigned long end,
+ unsigned int stride_shift)
+{
+ return ((end - start) >> stride_shift) > tlb_single_page_flush_ceiling;
+}
+
static void init_flush_tlb_info(struct flush_tlb_info *info,
struct mm_struct *mm,
unsigned long start, unsigned long end,
@@ -1383,7 +1389,7 @@ static void init_flush_tlb_info(struct flush_tlb_info *info,
* If the number of flushes is so large that a full flush
* would be faster, do a full flush.
*/
- if ((end - start) >> stride_shift > tlb_single_page_flush_ceiling) {
+ if (tlb_range_exceeds_ceiling(start, end, stride_shift)) {
start = 0;
end = TLB_FLUSH_ALL;
}
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 4/5] x86/mm: Initialize kernel TLB flush requests directly
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
` (2 preceding siblings ...)
2026-10-12 3:41 ` [PATCH v3 3/5] x86/mm: Extract the TLB range flush threshold check Chuyi Zhou
@ 2026-10-12 3:41 ` Chuyi Zhou
2026-10-12 3:41 ` [PATCH v3 5/5] x86/mm: Re-enable preemption before waiting for kernel TLB flushes Chuyi Zhou
2026-10-14 15:38 ` [PATCH v3 0/5] x86/mm: Allow preemption while " Sebastian Andrzej Siewior
5 siblings, 0 replies; 7+ messages in thread
From: Chuyi Zhou @ 2026-10-12 3:41 UTC (permalink / raw)
To: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, bigeasy, clrkwllms, rostedt, nadav.amit
Cc: linux-kernel, Chuyi Zhou
flush_tlb_kernel_range() uses init_flush_tlb_info(), which records
initiating_cpu with smp_processor_id(). The kernel flush callbacks do
not use this field, but initializing it in a preemptible context would
trigger the CPU-pinning check.
Prepare for allowing preemption during the IPI completion wait by
initializing start and end directly in struct flush_tlb_info and using
the shared threshold predicate to select full or range flushes. Keep the
outer preemption guard unchanged.
Signed-off-by: Chuyi Zhou <zhouchuyi@bytedance.com>
Link: https://lore.kernel.org/20260522104818.CbT5fyN8@linutronix.de/
---
arch/x86/mm/tlb.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index 0d03011c6825..2213f1a6f9e1 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1504,13 +1504,14 @@ static void kernel_tlb_flush_range(struct flush_tlb_info *info)
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
- struct flush_tlb_info info;
+ struct flush_tlb_info info = {
+ .start = start,
+ .end = end,
+ };
guard(preempt)();
- init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false,
- TLB_GENERATION_INVALID);
- if (info.end == TLB_FLUSH_ALL)
+ if (tlb_range_exceeds_ceiling(start, end, PAGE_SHIFT))
flush_tlb_all();
else
kernel_tlb_flush_range(&info);
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 5/5] x86/mm: Re-enable preemption before waiting for kernel TLB flushes
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
` (3 preceding siblings ...)
2026-10-12 3:41 ` [PATCH v3 4/5] x86/mm: Initialize kernel TLB flush requests directly Chuyi Zhou
@ 2026-10-12 3:41 ` Chuyi Zhou
2026-10-14 15:38 ` [PATCH v3 0/5] x86/mm: Allow preemption while " Sebastian Andrzej Siewior
5 siblings, 0 replies; 7+ messages in thread
From: Chuyi Zhou @ 2026-10-12 3:41 UTC (permalink / raw)
To: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, bigeasy, clrkwllms, rostedt, nadav.amit
Cc: linux-kernel, Chuyi Zhou
flush_tlb_kernel_range() uses on_each_cpu() to synchronously flush
kernel mappings on all online CPUs when using the IPI backend.
The synchronous wait can become longer as the number of online CPUs
grows, and a remote CPU with interrupts disabled can further delay
completion. Preemption remains disabled throughout this wait, delaying
higher-priority tasks on the initiating CPU. The outer guard prevents
the SMP layer from making its final completion wait preemptible.
The flush_tlb_info descriptor is allocated on the caller's stack and
remains valid until on_each_cpu() returns. The SMP layer protects CPU
selection, IPI queueing and local callback execution, so the kernel
flush caller does not need to remain pinned while waiting for remote
completion.
INVLPGB requires separate protection: TLBSYNC only waits for operations
issued on the same CPU. Disable preemption inside
invlpgb_kernel_range_flush() across the broadcast loop and TLBSYNC.
The full-flush helper invlpgb_flush_all() already provides this
protection.
Remove the outer preemption guard from flush_tlb_kernel_range() so the
IPI completion wait can be preempted when the caller's context allows
it. Keep both flush backends synchronous.
Signed-off-by: Chuyi Zhou <zhouchuyi@bytedance.com>
---
arch/x86/mm/tlb.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index 2213f1a6f9e1..0deec9e664b8 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1468,6 +1468,9 @@ static void invlpgb_kernel_range_flush(struct flush_tlb_info *info)
{
unsigned long addr, nr;
+ /* Keep the INVLPGB operations and TLBSYNC on the same CPU. */
+ guard(preempt)();
+
for (addr = info->start; addr < info->end; addr += nr << PAGE_SHIFT) {
nr = (info->end - addr) >> PAGE_SHIFT;
@@ -1509,8 +1512,6 @@ void flush_tlb_kernel_range(unsigned long start, unsigned long end)
.end = end,
};
- guard(preempt)();
-
if (tlb_range_exceeds_ceiling(start, end, PAGE_SHIFT))
flush_tlb_all();
else
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes
2026-10-12 3:41 [PATCH v3 0/5] x86/mm: Allow preemption while waiting for kernel TLB flushes Chuyi Zhou
` (4 preceding siblings ...)
2026-10-12 3:41 ` [PATCH v3 5/5] x86/mm: Re-enable preemption before waiting for kernel TLB flushes Chuyi Zhou
@ 2026-10-14 15:38 ` Sebastian Andrzej Siewior
5 siblings, 0 replies; 7+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-10-14 15:38 UTC (permalink / raw)
To: Chuyi Zhou
Cc: tglx, mingo, luto, peterz, paulmck, muchun.song, bp, dave.hansen,
pbonzini, clrkwllms, rostedt, nadav.amit, linux-kernel
On 2026-10-12 11:41:20 [+0800], Chuyi Zhou wrote:
> Changes in v3:
> - Patch 4: Retain struct flush_tlb_info in the kernel range helpers and
> initialize start/end directly, keeping the shared descriptor for
> future consolidation of the kernel and user paths (Nadav).
>
> v2: https://lore.kernel.org/cover.1791179421.git.zhouchuyi@bytedance.com/
I did prefer the v2 simply because now only ->start and ->end of struct
flush_tlb_info is initialized and the other fields are not. This is not
used by kernel_tlb_flush_range() (and lower) as of today but who knows…
Anyway, I am not calling the shots here. From the clean up point of view
it looks good to me, so
Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Sebastian
^ permalink raw reply [flat|nested] 7+ messages in thread