[PATCH 4/5] x86/mm: Decouple kernel TLB flushes from flush_tlb_info

From: Chuyi Zhou

Date: Mon Sep 21 2026 - 06:27:51 EST


Kernel range flushes only need the start and end addresses, but reuse
flush_tlb_info and its initialization of mm state, TLB generations and
the initiating CPU. None of those fields is consumed by the kernel
flush callbacks. In particular, initializing initiating_cpu imposes a
CPU-pinning requirement on a path that does not use it.

Select the full or range flush directly in flush_tlb_kernel_range(),
using the shared threshold predicate and an explicit TLB_FLUSH_ALL
check. Keep init_flush_tlb_info() and its smp_processor_id() check for
the mm paths.

Pass start and end directly through the kernel range helpers. Package
them in a private kernel_tlb_range only for the IPI callback, retaining
the existing payload alignment. The synchronous on_each_cpu() call
keeps the stack descriptor alive until all callbacks have completed.

Retain the outer preemption guard in flush_tlb_kernel_range() so the
descriptor refactoring does not change the preemption behavior.

Signed-off-by: Chuyi Zhou <zhouchuyi@xxxxxxxxxxxxx>
Link: https://lore.kernel.org/20260522104818.CbT5fyN8@xxxxxxxxxxxxx/
---
arch/x86/mm/tlb.c | 41 +++++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 16 deletions(-)

diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c
index f0dfeb271c66..4f9f0a18dbbc 100644
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1469,12 +1469,12 @@ void flush_tlb_all(void)
}

/* Flush an arbitrarily large range of memory with INVLPGB. */
-static void invlpgb_kernel_range_flush(struct flush_tlb_info *info)
+static void invlpgb_kernel_range_flush(unsigned long start, unsigned long end)
{
unsigned long addr, nr;

- for (addr = info->start; addr < info->end; addr += nr << PAGE_SHIFT) {
- nr = (info->end - addr) >> PAGE_SHIFT;
+ for (addr = start; addr < end; addr += nr << PAGE_SHIFT) {
+ nr = (end - addr) >> PAGE_SHIFT;

/*
* INVLPGB has a limit on the size of ranges it can
@@ -1487,38 +1487,47 @@ static void invlpgb_kernel_range_flush(struct flush_tlb_info *info)
__tlbsync();
}

+/* Preserve the alignment of the IPI payload shared with remote CPUs. */
+struct kernel_tlb_range {
+ unsigned long start;
+ unsigned long end;
+} __aligned(FLUSH_TLB_INFO_ALIGN);
+
static void do_kernel_range_flush(void *info)
{
- struct flush_tlb_info *f = info;
+ const struct kernel_tlb_range *range = info;
unsigned long addr;

/* flush range by one by one 'invlpg' */
- for (addr = f->start; addr < f->end; addr += PAGE_SIZE)
+ for (addr = range->start; addr < range->end; addr += PAGE_SIZE)
flush_tlb_one_kernel(addr);
}

-static void kernel_tlb_flush_range(struct flush_tlb_info *info)
+static void kernel_tlb_flush_range(unsigned long start, unsigned long end)
{
count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);

- if (cpu_feature_enabled(X86_FEATURE_INVLPGB))
- invlpgb_kernel_range_flush(info);
- else
- on_each_cpu(do_kernel_range_flush, info, 1);
+ if (cpu_feature_enabled(X86_FEATURE_INVLPGB)) {
+ invlpgb_kernel_range_flush(start, end);
+ } else {
+ struct kernel_tlb_range range = {
+ .start = start,
+ .end = end,
+ };
+
+ on_each_cpu(do_kernel_range_flush, &range, 1);
+ }
}

void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
- struct flush_tlb_info info;
-
guard(preempt)();
- init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false,
- TLB_GENERATION_INVALID);

- if (info.end == TLB_FLUSH_ALL)
+ if (end == TLB_FLUSH_ALL ||
+ tlb_range_exceeds_ceiling(start, end, PAGE_SHIFT))
kernel_tlb_flush_all();
else
- kernel_tlb_flush_range(&info);
+ kernel_tlb_flush_range(start, end);
}

/*
--
2.20.1