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

[IA64] Fix race in mm-context wrap-around logic.

The patch below should fix a race which could cause stale TLB entries.
Specifically, when 2 CPUs ended up racing for entrance to
wrap_mmu_context(). The losing CPU would find that by the time it
acquired ctx.lock, mm->context already had a valid value, but then it
failed to (re-)check the delayed TLB flushing logic and hence could
end up using a context number when there were still stale entries in
its TLB. The fix is to check for delayed TLB flushes only after
mm->context is valid (non-zero). The patch also makes GCC v4.x
happier by defining a non-volatile variant of mm_context_t called
nv_mm_context_t.

Signed-off-by: David Mosberger-Tang <David.Mosberger@acm.org>
Signed-off-by: Tony Luck <tony.luck@intel.com>

authored by

David Mosberger-Tang and committed by
Tony Luck
badea125 7d69fa62

+36 -24
+5 -3
include/asm-ia64/mmu.h
··· 2 2 #define __MMU_H 3 3 4 4 /* 5 - * Type for a context number. We declare it volatile to ensure proper ordering when it's 6 - * accessed outside of spinlock'd critical sections (e.g., as done in activate_mm() and 7 - * init_new_context()). 5 + * Type for a context number. We declare it volatile to ensure proper 6 + * ordering when it's accessed outside of spinlock'd critical sections 7 + * (e.g., as done in activate_mm() and init_new_context()). 8 8 */ 9 9 typedef volatile unsigned long mm_context_t; 10 + 11 + typedef unsigned long nv_mm_context_t; 10 12 11 13 #endif
+31 -21
include/asm-ia64/mmu_context.h
··· 55 55 delayed_tlb_flush (void) 56 56 { 57 57 extern void local_flush_tlb_all (void); 58 + unsigned long flags; 58 59 59 60 if (unlikely(__ia64_per_cpu_var(ia64_need_tlb_flush))) { 60 - local_flush_tlb_all(); 61 - __ia64_per_cpu_var(ia64_need_tlb_flush) = 0; 61 + spin_lock_irqsave(&ia64_ctx.lock, flags); 62 + { 63 + if (__ia64_per_cpu_var(ia64_need_tlb_flush)) { 64 + local_flush_tlb_all(); 65 + __ia64_per_cpu_var(ia64_need_tlb_flush) = 0; 66 + } 67 + } 68 + spin_unlock_irqrestore(&ia64_ctx.lock, flags); 62 69 } 63 70 } 64 71 65 - static inline mm_context_t 72 + static inline nv_mm_context_t 66 73 get_mmu_context (struct mm_struct *mm) 67 74 { 68 75 unsigned long flags; 69 - mm_context_t context = mm->context; 76 + nv_mm_context_t context = mm->context; 70 77 71 - if (context) 72 - return context; 73 - 74 - spin_lock_irqsave(&ia64_ctx.lock, flags); 75 - { 76 - /* re-check, now that we've got the lock: */ 77 - context = mm->context; 78 - if (context == 0) { 79 - cpus_clear(mm->cpu_vm_mask); 80 - if (ia64_ctx.next >= ia64_ctx.limit) 81 - wrap_mmu_context(mm); 82 - mm->context = context = ia64_ctx.next++; 78 + if (unlikely(!context)) { 79 + spin_lock_irqsave(&ia64_ctx.lock, flags); 80 + { 81 + /* re-check, now that we've got the lock: */ 82 + context = mm->context; 83 + if (context == 0) { 84 + cpus_clear(mm->cpu_vm_mask); 85 + if (ia64_ctx.next >= ia64_ctx.limit) 86 + wrap_mmu_context(mm); 87 + mm->context = context = ia64_ctx.next++; 88 + } 83 89 } 90 + spin_unlock_irqrestore(&ia64_ctx.lock, flags); 84 91 } 85 - spin_unlock_irqrestore(&ia64_ctx.lock, flags); 92 + /* 93 + * Ensure we're not starting to use "context" before any old 94 + * uses of it are gone from our TLB. 95 + */ 96 + delayed_tlb_flush(); 97 + 86 98 return context; 87 99 } 88 100 ··· 116 104 } 117 105 118 106 static inline void 119 - reload_context (mm_context_t context) 107 + reload_context (nv_mm_context_t context) 120 108 { 121 109 unsigned long rid; 122 110 unsigned long rid_incr = 0; ··· 150 138 static inline void 151 139 activate_context (struct mm_struct *mm) 152 140 { 153 - mm_context_t context; 141 + nv_mm_context_t context; 154 142 155 143 do { 156 144 context = get_mmu_context(mm); ··· 169 157 static inline void 170 158 activate_mm (struct mm_struct *prev, struct mm_struct *next) 171 159 { 172 - delayed_tlb_flush(); 173 - 174 160 /* 175 161 * We may get interrupts here, but that's OK because interrupt handlers cannot 176 162 * touch user-space.