[PATCH v3 5/5] x86/mm: Re-enable preemption before waiting for kernel TLB flushes

From: Chuyi Zhou

Date: Sun Oct 11 2026 - 23:45:14 EST


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@xxxxxxxxxxxxx>
---
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