[PATCH] ARM: tlb: Prevent flushing insane large ranges one by one

Robin Murphy robin.murphy at arm.com
Wed May 24 03:18:12 PDT 2023


On 2023-05-24 10:32, Thomas Gleixner wrote:
> vmalloc uses lazy TLB flushes for unmapped ranges to avoid excessive TLB
> flushing on every unmap. The lazy flushing coalesces unmapped ranges and
> invokes flush_tlb_kernel_range() with the combined range.
> 
> The coalescing can result in ranges which spawn the full vmalloc address
> range. In the case of flushing an executable mapping in the module address
> space this range is extended to also flush the direct map alias.
> 
> flush_tlb_kernel_range() then walks insane large ranges, the worst case
> observed was ~1.5GB.
> 
> The range is flushed page by page, which takes several milliseconds to
> complete in the worst case and obviously affects all processes in the
> system. In the worst case observed this causes the runtime of a realtime
> task on an isolated CPU to be almost doubled over the normal worst
> case, which makes it miss the deadline.
> 
> Cure this by sanity checking the range against a threshold and fall back to
> tlb_flush_all() when the range is too large.
> 
> The default threshold is 32 pages, but for CPUs with CP15 this is evaluated
> at boot time via read_cpuid(CPUID_TLBTYPE) and set to the half of the TLB
> size.
> 
> The vmalloc range coalescing could be improved to provide a list or
> array of ranges to flush, which allows to avoid overbroad flushing, but
> that's a major surgery and does not solve the problem of actual
> justified large range flushes which can happen due to the lazy flush
> mechanics in vmalloc. The lazy flush results in batching which is biased
> towards large range flushes by design.
> 
> Fixes: db64fe02258f ("mm: rewrite vmap layer")
> Reported-by: John Ogness <john.ogness at linutronix.de>
> Debugged-by: John Ogness <john.ogness at linutronix.de>
> Signed-off-by: Thomas Gleixner <tglx at linutronix.de>
> Tested-by: John Ogness <john.ogness at linutronix.de>
> Link: https://lore.kernel.org/all/87a5y5a6kj.ffs@tglx
> ---
>   arch/arm/include/asm/cputype.h  |    5 +++++
>   arch/arm/include/asm/tlbflush.h |    2 ++
>   arch/arm/kernel/setup.c         |   10 ++++++++++
>   arch/arm/kernel/smp_tlb.c       |    4 ++++
>   4 files changed, 21 insertions(+)
> 
> --- a/arch/arm/include/asm/cputype.h
> +++ b/arch/arm/include/asm/cputype.h
> @@ -196,6 +196,11 @@ static inline unsigned int __attribute_c
>   	return read_cpuid(CPUID_MPUIR);
>   }
>   
> +static inline unsigned int __attribute_const__ read_cpuid_tlbsize(void)
> +{
> +	return 64 << ((read_cpuid(CPUID_TLBTYPE) >> 1) & 0x03);
> +}

This appears to be specific to Cortex-A9 - these bits are 
implementation-defined, and it looks like on on most other Arm Ltd. CPUs 
they have no meaning at all, e.g.[1][2][3], but they could still hold 
some wildly unrelated value on other implementations.

Thanks,
Robin.

[1] 
https://developer.arm.com/documentation/ddi0344/k/system-control-coprocessor/system-control-coprocessor-registers/c0--tlb-type-register
[2] 
https://developer.arm.com/documentation/ddi0464/f/System-Control/Register-descriptions/TLB-Type-Register
[3] 
https://developer.arm.com/documentation/ddi0500/j/System-Control/AArch32-register-descriptions/TLB-Type-Register

> +
>   #elif defined(CONFIG_CPU_V7M)
>   
>   static inline unsigned int __attribute_const__ read_cpuid_id(void)
> --- a/arch/arm/include/asm/tlbflush.h
> +++ b/arch/arm/include/asm/tlbflush.h
> @@ -210,6 +210,8 @@ struct cpu_tlb_fns {
>   	unsigned long tlb_flags;
>   };
>   
> +extern unsigned int tlb_flush_all_threshold;
> +
>   /*
>    * Select the calling method
>    */
> --- a/arch/arm/kernel/setup.c
> +++ b/arch/arm/kernel/setup.c
> @@ -90,6 +90,8 @@ EXPORT_SYMBOL(__machine_arch_type);
>   unsigned int cacheid __read_mostly;
>   EXPORT_SYMBOL(cacheid);
>   
> +unsigned int tlb_flush_all_threshold __ro_after_init = 32;
> +
>   unsigned int __atags_pointer __initdata;
>   
>   unsigned int system_rev;
> @@ -356,6 +358,13 @@ static void __init cacheid_init(void)
>   		cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown");
>   }
>   
> +static void __init tlbsize_init(void)
> +{
> +#ifdef CONFIG_CPU_CP15
> +	tlb_flush_all_threshold = read_cpuid_tlbsize() / 2;
> +#endif
> +}
> +
>   /*
>    * These functions re-use the assembly code in head.S, which
>    * already provide the required functionality.
> @@ -747,6 +756,7 @@ static void __init setup_processor(void)
>   	elf_hwcap_fixup();
>   
>   	cacheid_init();
> +	tlbsize_init();
>   	cpu_init();
>   }
>   
> --- a/arch/arm/kernel/smp_tlb.c
> +++ b/arch/arm/kernel/smp_tlb.c
> @@ -234,6 +234,10 @@ void flush_tlb_range(struct vm_area_stru
>   
>   void flush_tlb_kernel_range(unsigned long start, unsigned long end)
>   {
> +	if ((end - start) > (tlb_flush_all_threshold << PAGE_SHIFT)) {
> +		flush_tlb_all();
> +		return;
> +	}
>   	if (tlb_ops_need_broadcast()) {
>   		struct tlb_args ta;
>   		ta.ta_start = start;
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel



More information about the linux-arm-kernel mailing list