Skip to content

Commit 5e10f98

Browse files
willdeaconctmarinas
authored andcommitted
arm64: mm: Fix TLBI vs ASID rollover
When switching to an 'mm_struct' for the first time following an ASID rollover, a new ASID may be allocated and assigned to 'mm->context.id'. This reassignment can happen concurrently with other operations on the mm, such as unmapping pages and subsequently issuing TLB invalidation. Consequently, we need to ensure that (a) accesses to 'mm->context.id' are atomic and (b) all page-table updates made prior to a TLBI using the old ASID are guaranteed to be visible to CPUs running with the new ASID. This was found by inspection after reviewing the VMID changes from Shameer but it looks like a real (yet hard to hit) bug. Cc: <[email protected]> Cc: Marc Zyngier <[email protected]> Cc: Jade Alglave <[email protected]> Cc: Shameer Kolothum <[email protected]> Signed-off-by: Will Deacon <[email protected]> Reviewed-by: Catalin Marinas <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Catalin Marinas <[email protected]>
1 parent ede3241 commit 5e10f98

File tree

2 files changed

+31
-9
lines changed

2 files changed

+31
-9
lines changed

arch/arm64/include/asm/mmu.h

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,32 @@ typedef struct {
2727
} mm_context_t;
2828

2929
/*
30-
* This macro is only used by the TLBI and low-level switch_mm() code,
31-
* neither of which can race with an ASID change. We therefore don't
32-
* need to reload the counter using atomic64_read().
30+
* We use atomic64_read() here because the ASID for an 'mm_struct' can
31+
* be reallocated when scheduling one of its threads following a
32+
* rollover event (see new_context() and flush_context()). In this case,
33+
* a concurrent TLBI (e.g. via try_to_unmap_one() and ptep_clear_flush())
34+
* may use a stale ASID. This is fine in principle as the new ASID is
35+
* guaranteed to be clean in the TLB, but the TLBI routines have to take
36+
* care to handle the following race:
37+
*
38+
* CPU 0 CPU 1 CPU 2
39+
*
40+
* // ptep_clear_flush(mm)
41+
* xchg_relaxed(pte, 0)
42+
* DSB ISHST
43+
* old = ASID(mm)
44+
* | <rollover>
45+
* | new = new_context(mm)
46+
* \-----------------> atomic_set(mm->context.id, new)
47+
* cpu_switch_mm(mm)
48+
* // Hardware walk of pte using new ASID
49+
* TLBI(old)
50+
*
51+
* In this scenario, the barrier on CPU 0 and the dependency on CPU 1
52+
* ensure that the page-table walker on CPU 1 *must* see the invalid PTE
53+
* written by CPU 0.
3354
*/
34-
#define ASID(mm) ((mm)->context.id.counter & 0xffff)
55+
#define ASID(mm) (atomic64_read(&(mm)->context.id) & 0xffff)
3556

3657
static inline bool arm64_kernel_unmapped_at_el0(void)
3758
{

arch/arm64/include/asm/tlbflush.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,10 @@ static inline void flush_tlb_all(void)
245245

246246
static inline void flush_tlb_mm(struct mm_struct *mm)
247247
{
248-
unsigned long asid = __TLBI_VADDR(0, ASID(mm));
248+
unsigned long asid;
249249

250250
dsb(ishst);
251+
asid = __TLBI_VADDR(0, ASID(mm));
251252
__tlbi(aside1is, asid);
252253
__tlbi_user(aside1is, asid);
253254
dsb(ish);
@@ -256,9 +257,10 @@ static inline void flush_tlb_mm(struct mm_struct *mm)
256257
static inline void flush_tlb_page_nosync(struct vm_area_struct *vma,
257258
unsigned long uaddr)
258259
{
259-
unsigned long addr = __TLBI_VADDR(uaddr, ASID(vma->vm_mm));
260+
unsigned long addr;
260261

261262
dsb(ishst);
263+
addr = __TLBI_VADDR(uaddr, ASID(vma->vm_mm));
262264
__tlbi(vale1is, addr);
263265
__tlbi_user(vale1is, addr);
264266
}
@@ -283,9 +285,7 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma,
283285
{
284286
int num = 0;
285287
int scale = 0;
286-
unsigned long asid = ASID(vma->vm_mm);
287-
unsigned long addr;
288-
unsigned long pages;
288+
unsigned long asid, addr, pages;
289289

290290
start = round_down(start, stride);
291291
end = round_up(end, stride);
@@ -305,6 +305,7 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma,
305305
}
306306

307307
dsb(ishst);
308+
asid = ASID(vma->vm_mm);
308309

309310
/*
310311
* When the CPU does not support TLB range operations, flush the TLB

0 commit comments

Comments
 (0)