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

s390/mm: fix race on mm->context.flush_mm

The order in __tlb_flush_mm_lazy is to flush TLB first and then clear
the mm->context.flush_mm bit. This can lead to missed flushes as the
bit can be set anytime, the order needs to be the other way aronud.

But this leads to a different race, __tlb_flush_mm_lazy may be called
on two CPUs concurrently. If mm->context.flush_mm is cleared first then
another CPU can bypass __tlb_flush_mm_lazy although the first CPU has
not done the flush yet. In a virtualized environment the time until the
flush is finally completed can be arbitrarily long.

Add a spinlock to serialize __tlb_flush_mm_lazy and use the function
in finish_arch_post_lock_switch as well.

Cc: <stable@vger.kernel.org>
Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>

+7 -3
+2
arch/s390/include/asm/mmu.h
··· 5 5 #include <linux/errno.h> 6 6 7 7 typedef struct { 8 + spinlock_t lock; 8 9 cpumask_t cpu_attach_mask; 9 10 atomic_t flush_count; 10 11 unsigned int flush_mm; ··· 28 27 } mm_context_t; 29 28 30 29 #define INIT_MM_CONTEXT(name) \ 30 + .context.lock = __SPIN_LOCK_UNLOCKED(name.context.lock), \ 31 31 .context.pgtable_lock = \ 32 32 __SPIN_LOCK_UNLOCKED(name.context.pgtable_lock), \ 33 33 .context.pgtable_list = LIST_HEAD_INIT(name.context.pgtable_list), \
+2 -2
arch/s390/include/asm/mmu_context.h
··· 17 17 static inline int init_new_context(struct task_struct *tsk, 18 18 struct mm_struct *mm) 19 19 { 20 + spin_lock_init(&mm->context.lock); 20 21 spin_lock_init(&mm->context.pgtable_lock); 21 22 INIT_LIST_HEAD(&mm->context.pgtable_list); 22 23 spin_lock_init(&mm->context.gmap_lock); ··· 122 121 while (atomic_read(&mm->context.flush_count)) 123 122 cpu_relax(); 124 123 cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm)); 125 - if (mm->context.flush_mm) 126 - __tlb_flush_mm(mm); 124 + __tlb_flush_mm_lazy(mm); 127 125 preempt_enable(); 128 126 } 129 127 set_fs(current->thread.mm_segment);
+3 -1
arch/s390/include/asm/tlbflush.h
··· 101 101 102 102 static inline void __tlb_flush_mm_lazy(struct mm_struct * mm) 103 103 { 104 + spin_lock(&mm->context.lock); 104 105 if (mm->context.flush_mm) { 105 - __tlb_flush_mm(mm); 106 106 mm->context.flush_mm = 0; 107 + __tlb_flush_mm(mm); 107 108 } 109 + spin_unlock(&mm->context.lock); 108 110 } 109 111 110 112 /*