Skip to content

Commit 72c0098

Browse files
amlutotorvalds
authored andcommitted
x86/mm: Reinitialize TLB state on hotplug and resume
When Linux brings a CPU down and back up, it switches to init_mm and then loads swapper_pg_dir into CR3. With PCID enabled, this has the side effect of masking off the ASID bits in CR3. This can result in some confusion in the TLB handling code. If we bring a CPU down and back up with any ASID other than 0, we end up with the wrong ASID active on the CPU after resume. This could cause our internal state to become corrupt, although major corruption is unlikely because init_mm doesn't have any user pages. More obviously, if CONFIG_DEBUG_VM=y, we'll trip over an assertion in the next context switch. The result of *that* is a failure to resume from suspend with probability 1 - 1/6^(cpus-1). Fix it by reinitializing cpu_tlbstate on resume and CPU bringup. Reported-by: Linus Torvalds <[email protected]> Reported-by: Jiri Kosina <[email protected]> Fixes: 10af623 ("x86/mm: Implement PCID based optimization: try to preserve old TLB entries using PCID") Signed-off-by: Andy Lutomirski <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 80cee03 commit 72c0098

File tree

4 files changed

+49
-0
lines changed

4 files changed

+49
-0
lines changed

arch/x86/include/asm/tlbflush.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ static inline void cr4_set_bits_and_update_boot(unsigned long mask)
198198
cr4_set_bits(mask);
199199
}
200200

201+
extern void initialize_tlbstate_and_flush(void);
202+
201203
static inline void __native_flush_tlb(void)
202204
{
203205
/*

arch/x86/kernel/cpu/common.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,7 @@ void cpu_init(void)
15831583
mmgrab(&init_mm);
15841584
me->active_mm = &init_mm;
15851585
BUG_ON(me->mm);
1586+
initialize_tlbstate_and_flush();
15861587
enter_lazy_tlb(&init_mm, me);
15871588

15881589
load_sp0(t, &current->thread);
@@ -1637,6 +1638,7 @@ void cpu_init(void)
16371638
mmgrab(&init_mm);
16381639
curr->active_mm = &init_mm;
16391640
BUG_ON(curr->mm);
1641+
initialize_tlbstate_and_flush();
16401642
enter_lazy_tlb(&init_mm, curr);
16411643

16421644
load_sp0(t, thread);

arch/x86/mm/tlb.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,50 @@ void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,
213213
switch_ldt(real_prev, next);
214214
}
215215

216+
/*
217+
* Call this when reinitializing a CPU. It fixes the following potential
218+
* problems:
219+
*
220+
* - The ASID changed from what cpu_tlbstate thinks it is (most likely
221+
* because the CPU was taken down and came back up with CR3's PCID
222+
* bits clear. CPU hotplug can do this.
223+
*
224+
* - The TLB contains junk in slots corresponding to inactive ASIDs.
225+
*
226+
* - The CPU went so far out to lunch that it may have missed a TLB
227+
* flush.
228+
*/
229+
void initialize_tlbstate_and_flush(void)
230+
{
231+
int i;
232+
struct mm_struct *mm = this_cpu_read(cpu_tlbstate.loaded_mm);
233+
u64 tlb_gen = atomic64_read(&init_mm.context.tlb_gen);
234+
unsigned long cr3 = __read_cr3();
235+
236+
/* Assert that CR3 already references the right mm. */
237+
WARN_ON((cr3 & CR3_ADDR_MASK) != __pa(mm->pgd));
238+
239+
/*
240+
* Assert that CR4.PCIDE is set if needed. (CR4.PCIDE initialization
241+
* doesn't work like other CR4 bits because it can only be set from
242+
* long mode.)
243+
*/
244+
WARN_ON(boot_cpu_has(X86_CR4_PCIDE) &&
245+
!(cr4_read_shadow() & X86_CR4_PCIDE));
246+
247+
/* Force ASID 0 and force a TLB flush. */
248+
write_cr3(cr3 & ~CR3_PCID_MASK);
249+
250+
/* Reinitialize tlbstate. */
251+
this_cpu_write(cpu_tlbstate.loaded_mm_asid, 0);
252+
this_cpu_write(cpu_tlbstate.next_asid, 1);
253+
this_cpu_write(cpu_tlbstate.ctxs[0].ctx_id, mm->context.ctx_id);
254+
this_cpu_write(cpu_tlbstate.ctxs[0].tlb_gen, tlb_gen);
255+
256+
for (i = 1; i < TLB_NR_DYN_ASIDS; i++)
257+
this_cpu_write(cpu_tlbstate.ctxs[i].ctx_id, 0);
258+
}
259+
216260
/*
217261
* flush_tlb_func_common()'s memory ordering requirement is that any
218262
* TLB fills that happen after we flush the TLB are ordered after we

arch/x86/power/cpu.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ static void fix_processor_context(void)
181181
#endif
182182
load_TR_desc(); /* This does ltr */
183183
load_mm_ldt(current->active_mm); /* This does lldt */
184+
initialize_tlbstate_and_flush();
184185

185186
fpu__resume_cpu();
186187

0 commit comments

Comments
 (0)