• Catalin Marinas's avatar
    arm64: asid: Do not replace active_asids if already 0 · a8ffaaa0
    Catalin Marinas authored
    Under some uncommon timing conditions, a generation check and
    xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
    an ASID roll-over on P2. If P2 has not seen the update to
    active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
    up waiting on the spinlock since the xchg() returned 0 while P2 can go
    through a second ASID roll-over with (T2,A1,G2) active on P2. This
    roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
    active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
    scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
    their generation bumped to G3:
    
    P1					P2
    --                                      --
    TTBR0.BADDR = T0
    TTBR0.ASID = A0
    asid_generation = G1
    check_and_switch_context(T1,A1,G1)
      generation match
    					check_and_switch_context(T2,A0,G0)
     				          new_context()
    					    ASID roll-over
    					    asid_generation = G2
    					    flush_context()
    					      active_asids[P1] = 0
    					      asid_map[A1] = 0
    					      reserved_asids[P1] = A0,G0
      xchg(active_asids, A1)
        active_asids[P1] = A1,G1
        xchg returns 0
      spin_lock_irqsave()
    					    allocated ASID (T2,A1,G2)
    					    asid_map[A1] = 1
    					  active_asids[P2] = A1,G2
    					...
    					check_and_switch_context(T3,A0,G0)
    					  new_context()
    					    ASID roll-over
    					    asid_generation = G3
    					    flush_context()
    					      active_asids[P1] = 0
    					      asid_map[A1] = 1
    					      reserved_asids[P1] = A1,G1
    					      reserved_asids[P2] = A1,G2
    					    allocated ASID (T3,A2,G3)
    					    asid_map[A2] = 1
    					  active_asids[P2] = A2,G3
      new_context()
        check_update_reserved_asid(A1,G1)
          matches reserved_asid[P1]
          reserved_asid[P1] = A1,G3
      updated T1 ASID to (T1,A1,G3)
    					check_and_switch_context(T2,A1,G2)
    					  new_context()
    					    check_and_switch_context(A1,G2)
    					      matches reserved_asids[P2]
    					      reserved_asids[P2] = A1,G3
    					  updated T2 ASID to (T2,A1,G3)
    
    At this point, we have two tasks, T1 and T2 both using ASID A1 with the
    latest generation G3. Any of them is allowed to be scheduled on the
    other CPU leading to two different tasks with the same ASID on the same
    CPU.
    
    This patch changes the xchg to cmpxchg so that the active_asids is only
    updated if non-zero to avoid a race with an ASID roll-over on a
    different CPU.
    
    The ASID allocation algorithm has been formally verified using the TLA+
    model checker (see
    https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
    for the spec).
    Reviewed-by: default avatarWill Deacon <will.deacon@arm.com>
    Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
    a8ffaaa0
context.c 7.49 KB