fixing tlb flush race problem on smp
RFC PATCH 1/1 fixing tlb flush race problem on smp
Many of us are aware of a hole in current TLB flushing code that could cause processes using the same ASID for a SMP machine.
Actually there are several problems:
1) get_new_mmu_context() and following set_entryhi, etc are not called automically in switch_mm() and active_mm(). If an IPI happens and request to flush local tlb, bad things happen.
2) if local_flush_tlb_range() and local_flush_tlb_mm() are called from an IPI, they may call get_new_mmu_context() which can bump up the ASID generation number with current active_mm totally not aware of it. Bad things will happen later.
3) during the time window after schedule() calling switch_mm() before switch_to(), current->active_mm may be valid but does really mean "current->active_mm" anymore. This is because the "current" process will soon become "prev". The real active_mm is actually "next->active_mm". Because of this, it is not enough for those two IPI'ed flushing routines to just check again current->active_mm. Long story made short - bad things will happen.
It turns out that other arches have similar problems and solved it in various ways. Unfortunely I like none of them.
Here is one I am pretty happy with. It is very small and efficient. And conceptually it is clean too. We basically keep the semantics of ->mm and ->active_mm unchanged and only introduce a new bit to mark which mm is the true owner of mmu hardware on a cpu.
The only downside is that cpu_vm_mask variable does not really mean "mask for blocking IPI" in this approach. It actually indicates whether current->active_mm is really active or not.
Tested and passed the notorious fork/malloc test.
Let me know what you think.
Jun
arch/mips/mm/tlb-sb1.c renamed from arch/mips/mm/tlb-sb1.c.orig +10 -3
@@ -172,9 +172,13 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) +if (mm == current->active_mm) { +get_new_mmu_context (mm, cpu);write_c0_entryhi (cpu_asid (cpu, mm)); + }else { +/* drop the current context completely */ +CPU_CONTEXT (cpu, mm) =0 ; + } } }__restore_flags (flags);
@@ -258,9 +262,12 @@__save_and_cli (flags); cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ());if (mm == current->active_mm) { +get_new_mmu_context (mm,smp_processor_id ());write_c0_entryhi (cpu_asid (cpu, mm)); + }else { +/* drop the current context completely */ +CPU_CONTEXT (cpu, mm) =0 ; } }__restore_flags (flags);
include/asm-mips/mmu_context.h renamed from include/asm-mips/mmu_context.h.orig +19 -0
@@ -89,12 +89,25 @@static inline void switch_mm (struct mm_struct *prev,struct mm_struct *next,struct task_struct *tsk,unsigned cpu) { +unsigned long flags; + +__save_and_cli (flags); +/* Check if our ASID is of an older version and thus invalid */ if ((cpu_context (cpu, next) ^asid_cache (cpu)) &ASID_VERSION_MASK )get_new_mmu_context (next, cpu);write_c0_entryhi (cpu_context (cpu, next));TLBMISS_HANDLER_SETUP_PGD (next->pgd); + +/* +* Mark current->active_mm as not "active" anymore. +* We don't want to mislead possible IPI tlb flush routines. +*/ +clear_bit (cpu, &prev->cpu_vm_mask); +set_bit (cpu, &next->cpu_vm_mask); + +__restore_flags (flags); }/*
@@ -112,11 +125,17 @@static inline void activate_mm (struct mm_struct *prev,struct mm_struct *next) { +unsigned long flags; + +__save_and_cli (flags); +/* Unconditionally get a new ASID. */ get_new_mmu_context (next,smp_processor_id ());write_c0_entryhi (cpu_context (smp_processor_id (), next));TLBMISS_HANDLER_SETUP_PGD (next->pgd); + +__restore_flags (flags); }#endif /* _ASM_MMU_CONTEXT_H */
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"jun" == Jun Sun <[email protected]> writes:
Hi
just nickpitting.
jun> + } else {
jun> + /* drop the current context completely */
jun> + CPU_CONTEXT(cpu, mm) = 0;
jun> }
jun> }
jun> __restore_flags(flags);
Perhaps creating a inline function for this, as the code is identical in both branches?
jun> diff -Nru link/include/asm-mips/mmu_context.h.orig link/include/asm-mips/mmu_context.h
jun> --- link/include/asm-mips/mmu_context.h.orig Tue Jan 21 13:55:43 2003
jun> +++ link/include/asm-mips/mmu_context.h Tue Jan 21 14:01:19 2003
jun> @@ -89,12 +89,25 @@
jun> static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, jun> struct task_struct *tsk, unsigned cpu)
jun> {
jun> + unsigned long flags;
jun> +
jun> + __save_and_cli(flags);
s/__save_and_cli()/local_irq_save()/
jun> + __restore_flags(flags);
s/__restore_flags()/local_irq_restore()/
Same in the other occurence, please.
Later, Juan.
Signature · 2 lines
In theory, practice and theory are the same, but in practice they are different -- Larry McVoy
Thanks, Juan.
I also find a stupid typo and a subtle hole in my original patch. Here is an updated version, for 2.4/mips only. If it looks ok, I will extend to other sub-arches/trees.
This new one is pretty nice in that all mmu related operations are put into one file and it is much easier to ensure correctness later.
Jun
arch/mips/mm/tlb-sb1.c renamed from arch/mips/mm/tlb-sb1.c.orig +2 -7
@@ -172,9 +172,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); } }__restore_flags (flags);
@@ -258,10 +256,7 @@__save_and_cli (flags); cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_asid (cpu, mm)); - } +drop_mmu_context (mm, cpu); }__restore_flags (flags); }
include/asm-mips/mmu_context.h renamed from include/asm-mips/mmu_context.h.orig +41 -0
64 lines not on this page · raw message
Quoted 7 lines · Jun Sun
I also find a stupid typo and a subtle hole in my original patch. Here is an updated version, for 2.4/mips only. If it looks ok, I will extend to other sub-arches/trees.
This new one is pretty nice in that all mmu related operations are put into one file and it is much easier to ensure correctness later.
I like this one.
include/asm-mips/mmu_context.h line 110
Quoted 9 lines
include/asm-mips/mmu_context.h renamed from include/asm-mips/mmu_context.h.orig +9
-0+ +/* +* Mark current->active_mm as not "active" anymore. +* We don't want to mislead possible IPI tlb flush routines. +*/ +clear_bit (cpu, &prev->cpu_vm_mask); +set_bit (cpu, &next->cpu_vm_mask); + +local_irq_restore (flags);
I don't think it's necessary to protect the clear_bit and set_bit operations with local_irq_save ... local_irq_restore.
In addition because switch_mm is always called with interrupts enabled you can simplify that to local_irq_disable ... local_irq_enable.
Ralf
Here is a complete patch for both mips/mips64, 2.4 and 2.5. Of course only 2.4/mips combo is tested.
The clear_bit/set_bit actually need to be protected. The flag setting needs to be in sync with the actual hardware setting (set_entry_hi/set current pgd). Otherwise an tlb flushing IPI may does the wrong thing.
Jun
Quoted 25 lines · Ralf Baechle
Quoted 7 lines · Jun Sun
I also find a stupid typo and a subtle hole in my original patch. Here is an updated version, for 2.4/mips only. If it looks ok, I will extend to other sub-arches/trees.
This new one is pretty nice in that all mmu related operations are put into one file and it is much easier to ensure correctness later.
I like this one.
Review
Quoted 9 lines
(quoted diff) +9
-0+ + /* + * Mark current->active_mm as not "active" anymore. + * We don't want to mislead possible IPI tlb flush routines. + */ + clear_bit(cpu, &prev->cpu_vm_mask); + set_bit(cpu, &next->cpu_vm_mask); + + local_irq_restore(flags);I don't think it's necessary to protect the
clear_bitandset_bitoperations withlocal_irq_save...local_irq_restore.
Quoted 5 lines
In addition because
switch_mmis always called with interrupts enabled you can simplify that tolocal_irq_disable...local_irq_enable.Ralf
arch/mips/mm/tlb-sb1.c renamed from arch/mips/mm/tlb-sb1.c.orig +3 -12
@@ -172,9 +172,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); } }local_irq_restore (flags);
@@ -253,17 +251,10 @@these entries, we just bump the asid. */ void local_flush_tlb_mm (struct mm_struct *mm) { -unsigned long flags; -int cpu; -local_irq_save (flags); - cpu =smp_processor_id (); +int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_asid (cpu, mm)); - } +drop_mmu_context (mm, cpu); } -local_irq_restore (flags); }/* Stolen from mips32 routines */
arch/mips/mm/tlb-r4k.c renamed from arch/mips/mm/tlb-r4k.c.orig +2 -10
@@ -76,16 +76,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" ,cpu_context (cpu, mm));#endif -local_irq_save (flags); -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -133,9 +127,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips/mm/tlb-r3k.c renamed from arch/mips/mm/tlb-r3k.c.orig +2 -10
@@ -69,16 +69,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%lu>]" , (unsigned long )cpu_context (cpu, mm));#endif -local_irq_save (flags); -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); -local_irq_restore (flags); +drop_mmu_context (mm, cpu); } }
@@ -119,9 +113,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips64/mm/tlb-sb1.c renamed from arch/mips64/mm/tlb-sb1.c.orig +3 -12
@@ -180,9 +180,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); } }local_irq_restore (flags);
@@ -231,17 +229,10 @@these entries, we just bump the asid. */ void local_flush_tlb_mm (struct mm_struct *mm) { -unsigned long flags; -int cpu; -local_irq_save (flags); - cpu =smp_processor_id (); +int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_asid (cpu, mm)); - } +drop_mmu_context (mm, cpu); } -local_irq_restore (flags); }/* Stolen from mips32 routines */
arch/mips64/mm/tlb-r4k.c renamed from arch/mips64/mm/tlb-r4k.c.orig +2 -10
@@ -80,16 +80,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" , mm->context);#endif -local_irq_save (flags); -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -137,9 +131,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips64/mm/tlb-andes.c renamed from arch/mips64/mm/tlb-andes.c.orig +4 -13
@@ -53,18 +53,12 @@void local_flush_tlb_mm (struct mm_struct *mm) { -if (cpu_context (smp_processor_id (), mm) !=0 ) { -unsigned long flags; - +int cpu =smp_processor_id (); +if (cpu_context (cpu, mm) !=0 ) {#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" , mm->context);#endif -local_irq_save (flags); -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (smp_processor_id (), mm) - &ASID_MASK ); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -106,10 +100,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (smp_processor_id (), mm) - &ASID_MASK ); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
include/asm-mips/mmu_context.h renamed from include/asm-mips/mmu_context.h.orig +41 -0
64 lines not on this page · raw message
include/asm-mips64/mmu_context.h renamed from include/asm-mips64/mmu_context.h.orig +41 -0
64 lines not on this page · raw message
arch/mips/mm/tlb-sb1.c renamed from arch/mips/mm/tlb-sb1.c.orig +3 -18
@@ -172,15 +172,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_asid (cpu, mm)); - }else if (!(cpu_asid (cpu, mm)) && -cpu_context (cpu, current->active_mm)) { -/* Just wrapped ASIDs, bump the active one */ -get_new_mmu_context (current->active_mm, cpu); -write_c0_entryhi (cpu_context (cpu, current->active_mm)&0xff ); - } +drop_mmu_context (mm, cpu); } }local_irq_restore (flags);
@@ -325,17 +317,10 @@these entries, we just bump the asid. */ void local_flush_tlb_mm (struct mm_struct *mm) { -unsigned long flags; -int cpu; -local_irq_save (flags); - cpu =smp_processor_id (); +int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_context (cpu, mm) &0xff ); - } +drop_mmu_context (mm, cpu); } -local_irq_restore (flags); }/* Stolen from mips32 routines */
arch/mips/mm/tlb-r4k.c renamed from arch/mips/mm/tlb-r4k.c.orig +2 -10
@@ -76,16 +76,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" ,cpu_context (cpu, mm));#endif -local_irq_save (flags); -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -134,9 +128,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips/mm/tlb-r3k.c renamed from arch/mips/mm/tlb-r3k.c.orig +2 -10
@@ -69,16 +69,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%lu>]" , (unsigned long )cpu_context (cpu, mm));#endif -local_irq_save (flags); -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); -local_irq_restore (flags); +drop_mmu_context (mm, cpu); } }
@@ -120,9 +114,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips64/mm/tlb-sb1.c renamed from arch/mips64/mm/tlb-sb1.c.orig +3 -12
@@ -180,9 +180,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (cpu, mm) &0xff ); +drop_mmu_context (mm, cpu); } }local_irq_restore (flags);
@@ -268,17 +266,10 @@these entries, we just bump the asid. */ void local_flush_tlb_mm (struct mm_struct *mm) { -unsigned long flags; -int cpu; -local_irq_save (flags); - cpu =smp_processor_id (); +int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) { -write_c0_entryhi (cpu_context (cpu, mm) &0xff ); - } +drop_mmu_context (mm, cpu); } -local_irq_restore (flags); }/* Stolen from mips32 routines */
arch/mips64/mm/tlb-r4k.c renamed from arch/mips64/mm/tlb-r4k.c.orig +2 -10
@@ -80,16 +80,10 @@int cpu =smp_processor_id ();if (cpu_context (cpu, mm) !=0 ) { -unsigned long flags; -#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" ,cpu_context (cpu, mm));#endif -local_irq_save (flags); -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -138,9 +132,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm, cpu); -if (mm == current->active_mm) -write_c0_entryhi (cpu_asid (cpu, mm)); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
arch/mips64/mm/tlb-andes.c renamed from arch/mips64/mm/tlb-andes.c.orig +4 -13
@@ -53,18 +53,12 @@void local_flush_tlb_mm (struct mm_struct *mm) { -if (cpu_context (smp_processor_id (), mm) !=0 ) { -unsigned long flags; - +int cpu =smp_processor_id (); +if (cpu_context (cpu, mm) !=0 ) {#ifdef DEBUG_TLB printk ("[tlbmm<%d>]" , mm->context);#endif -local_irq_save (flags); -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (smp_processor_id (), mm) - &ASID_MASK ); -local_irq_restore (flags); +drop_mmu_context (mm,cpu); } }
@@ -108,10 +102,7 @@ }write_c0_entryhi (oldpid); }else { -get_new_mmu_context (mm,smp_processor_id ()); -if (mm == current->active_mm) -write_c0_entryhi (cpu_context (smp_processor_id (), mm) - &ASID_MASK ); +drop_mmu_context (mm, cpu); }local_irq_restore (flags); }
include/asm-mips/mmu_context.h renamed from include/asm-mips/mmu_context.h.orig +41 -0
64 lines not on this page · raw message
include/asm-mips64/mmu_context.h renamed from include/asm-mips64/mmu_context.h.orig +41 -0
64 lines not on this page · raw message
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line · Jun Sun
jsun> Here is a complete patch for both mips/mips64, 2.4 and 2.5. Of jsun> course only 2.4/mips combo is tested.
The attached patch seems to break r3k codes. Here is a patch to fix it (only for 2.4/mips).
include/asm-mips/mmu_context.h +1 -1
@@ -151,7 +151,7 @@if (test_bit (cpu, &mm->cpu_vm_mask)) {get_new_mmu_context (mm, cpu); -write_c0_entryhi (cpu_context (cpu, mm) &0xff ); +write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); }else {/* will get a new context next time */ cpu_context (cpu, mm) =0 ;
---
Atsushi Nemoto
Quoted 7 lines · Atsushi Nemoto
The attached patch seems to break r3k codes. Here is a patch to fix it (only for 2.4/mips).
diff -ur
linux-mips-cvs/include/asm-mips/mmu_context.hlinux.new/include/asm-mips/mmu_context.h
include/asm-mips/mmu_context.h line 157
Quoted 8 lines
include/asm-mips/mmu_context.h +1
-1@@ -151,7 +151,7 @@if (test_bit (cpu, &mm->cpu_vm_mask)) {get_new_mmu_context (mm, cpu); -write_c0_entryhi (cpu_context (cpu, mm) &0xff ); +write_c0_entryhi (cpu_context (cpu, mm) &ASID_MASK ); }else {/* will get a new context next time */ cpu_context (cpu, mm) =0 ;
I've checked in a slightly different fix. Thanks for spotting the
problem.
Signature · 3 lines
+ Maciej W. Rozycki, Technical University of Gdansk, Poland + +--------------------------------------------------------------+ + e-mail: [email protected], PGP key available +
Quoted 9 lines · Juan Quintela
jun> +
__save_and_cli(flags);s/__save_and_cli()/local_irq_save()/
jun> +
__restore_flags(flags);s/__restore_flags()/local_irq_restore()/
Same in the other occurence, please.
I've already done this recently for large parts of arch/mips* and include/asm-mips*.
Ralf