Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

riscv: Add support for BATCHED_UNMAP_TLB_FLUSH

Allow to defer the flushing of the TLB when unmapping pages, which allows
to reduce the numbers of IPI and the number of sfence.vma.

The ubenchmarch used in commit 43b3dfdd0455 ("arm64: support
batched/deferred tlb shootdown during page reclamation/migration") that
was multithreaded to force the usage of IPI shows good performance
improvement on all platforms:

* Unmatched: ~34%
* TH1520 : ~78%
* Qemu : ~81%

In addition, perf on qemu reports an important decrease in time spent
dealing with IPIs:

Before: 68.17% main [kernel.kallsyms] [k] __sbi_rfence_v02_call
After : 8.64% main [kernel.kallsyms] [k] __sbi_rfence_v02_call

* Benchmark:

int stick_this_thread_to_core(int core_id) {
int num_cores = sysconf(_SC_NPROCESSORS_ONLN);
if (core_id < 0 || core_id >= num_cores)
return EINVAL;

cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);

pthread_t current_thread = pthread_self();
return pthread_setaffinity_np(current_thread,
sizeof(cpu_set_t), &cpuset);
}

static void *fn_thread (void *p_data)
{
int ret;
pthread_t thread;

stick_this_thread_to_core((int)p_data);

while (1) {
sleep(1);
}

return NULL;
}

int main()
{
volatile unsigned char *p = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
pthread_t threads[4];
int ret;

for (int i = 0; i < 4; ++i) {
ret = pthread_create(&threads[i], NULL, fn_thread, (void *)i);
if (ret)
{
printf("%s", strerror (ret));
}
}

memset(p, 0x88, SIZE);

for (int k = 0; k < 10000; k++) {
/* swap in */
for (int i = 0; i < SIZE; i += 4096) {
(void)p[i];
}

/* swap out */
madvise(p, SIZE, MADV_PAGEOUT);
}

for (int i = 0; i < 4; i++)
{
pthread_cancel(threads[i]);
}

for (int i = 0; i < 4; i++)
{
pthread_join(threads[i], NULL);
}

return 0;
}

Signed-off-by: Alexandre Ghiti <alexghiti@rivosinc.com>
Reviewed-by: Jisheng Zhang <jszhang@kernel.org>
Tested-by: Jisheng Zhang <jszhang@kernel.org> # Tested on TH1520
Tested-by: Nam Cao <namcao@linutronix.de>
Link: https://lore.kernel.org/r/20240108193640.344929-1-alexghiti@rivosinc.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>

authored by

Alexandre Ghiti and committed by
Palmer Dabbelt
54d7431a ff172d48

+74 -21
+1 -1
Documentation/features/vm/TLB/arch-support.txt
··· 20 20 | openrisc: | .. | 21 21 | parisc: | TODO | 22 22 | powerpc: | TODO | 23 - | riscv: | TODO | 23 + | riscv: | ok | 24 24 | s390: | TODO | 25 25 | sh: | TODO | 26 26 | sparc: | TODO |
+1
arch/riscv/Kconfig
··· 53 53 select ARCH_USE_MEMTEST 54 54 select ARCH_USE_QUEUED_RWLOCKS 55 55 select ARCH_USES_CFI_TRAPS if CFI_CLANG 56 + select ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH if SMP && MMU 56 57 select ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT if MMU 57 58 select ARCH_WANT_FRAME_POINTERS 58 59 select ARCH_WANT_GENERAL_HUGETLB if !RISCV_ISA_SVNAPOT
+15
arch/riscv/include/asm/tlbbatch.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* 3 + * Copyright (C) 2023 Rivos Inc. 4 + */ 5 + 6 + #ifndef _ASM_RISCV_TLBBATCH_H 7 + #define _ASM_RISCV_TLBBATCH_H 8 + 9 + #include <linux/cpumask.h> 10 + 11 + struct arch_tlbflush_unmap_batch { 12 + struct cpumask cpumask; 13 + }; 14 + 15 + #endif /* _ASM_RISCV_TLBBATCH_H */
+8
arch/riscv/include/asm/tlbflush.h
··· 46 46 void flush_pmd_tlb_range(struct vm_area_struct *vma, unsigned long start, 47 47 unsigned long end); 48 48 #endif 49 + 50 + bool arch_tlbbatch_should_defer(struct mm_struct *mm); 51 + void arch_tlbbatch_add_pending(struct arch_tlbflush_unmap_batch *batch, 52 + struct mm_struct *mm, 53 + unsigned long uaddr); 54 + void arch_flush_tlb_batched_pending(struct mm_struct *mm); 55 + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch); 56 + 49 57 #else /* CONFIG_SMP && CONFIG_MMU */ 50 58 51 59 #define flush_tlb_all() local_flush_tlb_all()
+49 -20
arch/riscv/mm/tlbflush.c
··· 93 93 local_flush_tlb_range_asid(d->start, d->size, d->stride, d->asid); 94 94 } 95 95 96 - static void __flush_tlb_range(struct mm_struct *mm, unsigned long start, 97 - unsigned long size, unsigned long stride) 96 + static void __flush_tlb_range(struct cpumask *cmask, unsigned long asid, 97 + unsigned long start, unsigned long size, 98 + unsigned long stride) 98 99 { 99 100 struct flush_tlb_range_data ftd; 100 - const struct cpumask *cmask; 101 - unsigned long asid = FLUSH_TLB_NO_ASID; 102 101 bool broadcast; 103 102 104 - if (mm) { 105 - unsigned int cpuid; 103 + if (cpumask_empty(cmask)) 104 + return; 106 105 107 - cmask = mm_cpumask(mm); 108 - if (cpumask_empty(cmask)) 109 - return; 106 + if (cmask != cpu_online_mask) { 107 + unsigned int cpuid; 110 108 111 109 cpuid = get_cpu(); 112 110 /* check if the tlbflush needs to be sent to other CPUs */ 113 111 broadcast = cpumask_any_but(cmask, cpuid) < nr_cpu_ids; 114 - 115 - if (static_branch_unlikely(&use_asid_allocator)) 116 - asid = atomic_long_read(&mm->context.id) & asid_mask; 117 112 } else { 118 - cmask = cpu_online_mask; 119 113 broadcast = true; 120 114 } 121 115 ··· 129 135 local_flush_tlb_range_asid(start, size, stride, asid); 130 136 } 131 137 132 - if (mm) 138 + if (cmask != cpu_online_mask) 133 139 put_cpu(); 140 + } 141 + 142 + static inline unsigned long get_mm_asid(struct mm_struct *mm) 143 + { 144 + return static_branch_unlikely(&use_asid_allocator) ? 145 + atomic_long_read(&mm->context.id) & asid_mask : FLUSH_TLB_NO_ASID; 134 146 } 135 147 136 148 void flush_tlb_mm(struct mm_struct *mm) 137 149 { 138 - __flush_tlb_range(mm, 0, FLUSH_TLB_MAX_SIZE, PAGE_SIZE); 150 + __flush_tlb_range(mm_cpumask(mm), get_mm_asid(mm), 151 + 0, FLUSH_TLB_MAX_SIZE, PAGE_SIZE); 139 152 } 140 153 141 154 void flush_tlb_mm_range(struct mm_struct *mm, 142 155 unsigned long start, unsigned long end, 143 156 unsigned int page_size) 144 157 { 145 - __flush_tlb_range(mm, start, end - start, page_size); 158 + __flush_tlb_range(mm_cpumask(mm), get_mm_asid(mm), 159 + start, end - start, page_size); 146 160 } 147 161 148 162 void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) 149 163 { 150 - __flush_tlb_range(vma->vm_mm, addr, PAGE_SIZE, PAGE_SIZE); 164 + __flush_tlb_range(mm_cpumask(vma->vm_mm), get_mm_asid(vma->vm_mm), 165 + addr, PAGE_SIZE, PAGE_SIZE); 151 166 } 152 167 153 168 void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, ··· 188 185 } 189 186 } 190 187 191 - __flush_tlb_range(vma->vm_mm, start, end - start, stride_size); 188 + __flush_tlb_range(mm_cpumask(vma->vm_mm), get_mm_asid(vma->vm_mm), 189 + start, end - start, stride_size); 192 190 } 193 191 194 192 void flush_tlb_kernel_range(unsigned long start, unsigned long end) 195 193 { 196 - __flush_tlb_range(NULL, start, end - start, PAGE_SIZE); 194 + __flush_tlb_range((struct cpumask *)cpu_online_mask, FLUSH_TLB_NO_ASID, 195 + start, end - start, PAGE_SIZE); 197 196 } 198 197 199 198 #ifdef CONFIG_TRANSPARENT_HUGEPAGE 200 199 void flush_pmd_tlb_range(struct vm_area_struct *vma, unsigned long start, 201 200 unsigned long end) 202 201 { 203 - __flush_tlb_range(vma->vm_mm, start, end - start, PMD_SIZE); 202 + __flush_tlb_range(mm_cpumask(vma->vm_mm), get_mm_asid(vma->vm_mm), 203 + start, end - start, PMD_SIZE); 204 204 } 205 205 #endif 206 + 207 + bool arch_tlbbatch_should_defer(struct mm_struct *mm) 208 + { 209 + return true; 210 + } 211 + 212 + void arch_tlbbatch_add_pending(struct arch_tlbflush_unmap_batch *batch, 213 + struct mm_struct *mm, 214 + unsigned long uaddr) 215 + { 216 + cpumask_or(&batch->cpumask, &batch->cpumask, mm_cpumask(mm)); 217 + } 218 + 219 + void arch_flush_tlb_batched_pending(struct mm_struct *mm) 220 + { 221 + flush_tlb_mm(mm); 222 + } 223 + 224 + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) 225 + { 226 + __flush_tlb_range(&batch->cpumask, FLUSH_TLB_NO_ASID, 0, 227 + FLUSH_TLB_MAX_SIZE, PAGE_SIZE); 228 + }