locking/lockdep: Exclude local_lock_t from IRQ inversions
authorBoqun Feng <boqun.feng@gmail.com>
Thu, 10 Dec 2020 10:15:00 +0000 (11:15 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Thu, 14 Jan 2021 10:20:17 +0000 (11:20 +0100)
commit5f2962401c6e195222f320d12b3a55377b2d4653
tree6cb7bcb9ae9e84185c866db87529c4be3348a38e
parent175b1a60e8805617d74aefe17ce0d3a32eceb55c
locking/lockdep: Exclude local_lock_t from IRQ inversions

The purpose of local_lock_t is to abstract: preempt_disable() /
local_bh_disable() / local_irq_disable(). These are the traditional
means of gaining access to per-cpu data, but are fundamentally
non-preemptible.

local_lock_t provides a per-cpu lock, that on !PREEMPT_RT reduces to
no-ops, just like regular spinlocks do on UP.

This gives rise to:

CPU0 CPU1

local_lock(B) spin_lock_irq(A)
<IRQ>
  spin_lock(A) local_lock(B)

Where lockdep then figures things will lock up; which would be true if
B were any other kind of lock. However this is a false positive, no
such deadlock actually exists.

For !RT the above local_lock(B) is preempt_disable(), and there's
obviously no deadlock; alternatively, CPU0's B != CPU1's B.

For RT the argument is that since local_lock() nests inside
spin_lock(), it cannot be used in hardirq context, and therefore CPU0
cannot in fact happen. Even though B is a real lock, it is a
preemptible lock and any threaded-irq would simply schedule out and
let the preempted task (which holds B) continue such that the task on
CPU1 can make progress, after which the threaded-irq resumes and can
finish.

This means that we can never form an IRQ inversion on a local_lock
dependency, so terminate the graph walk when looking for IRQ
inversions when we encounter one.

One consequence is that (for LOCKDEP_SMALL) when we look for redundant
dependencies, A -> B is not redundant in the presence of A -> L -> B.

Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
[peterz: Changelog]
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
kernel/locking/lockdep.c