From: Sebastian Andrzej Siewior <bige...@linutronix.de>

In !PREEMPT_RT local_lock_irqsave() disables interrupts to protect
critical section, but it doesn't prevent NMI, so the fully reentrant
code cannot use local_lock_irqsave() for exclusive access.

Introduce localtry_lock_t and localtry_lock_irqsave() that
disables interrupts and sets acquired=1, so localtry_lock_irqsave()
from NMI attempting to acquire the same lock will return false.

In PREEMPT_RT local_lock_irqsave() maps to preemptible spin_lock().
Map localtry_lock_irqsave() to preemptible spin_trylock().
When in hard IRQ or NMI return false right away, since
spin_trylock() is not safe due to PI issues.

Note there is no need to use local_inc for acquired variable,
since it's a percpu variable with strict nesting scopes.

Signed-off-by: Sebastian Andrzej Siewior <bige...@linutronix.de>
Signed-off-by: Alexei Starovoitov <a...@kernel.org>
Signed-off-by: Vlastimil Babka <vba...@suse.cz>
---
 include/linux/local_lock.h          |  59 +++++++++++++++++
 include/linux/local_lock_internal.h | 123 ++++++++++++++++++++++++++++++++++++
 2 files changed, 182 insertions(+)

diff --git a/include/linux/local_lock.h b/include/linux/local_lock.h
index 
091dc0b6bdfb9f4721f94d19828a38fbfa59346c..05c254a5d7d3e6db64d7f81a3a4a10f5a942c29e
 100644
--- a/include/linux/local_lock.h
+++ b/include/linux/local_lock.h
@@ -51,6 +51,65 @@
 #define local_unlock_irqrestore(lock, flags)                   \
        __local_unlock_irqrestore(lock, flags)
 
+/**
+ * localtry_lock_init - Runtime initialize a lock instance
+ */
+#define localtry_lock_init(lock)               __localtry_lock_init(lock)
+
+/**
+ * localtry_lock - Acquire a per CPU local lock
+ * @lock:      The lock variable
+ */
+#define localtry_lock(lock)            __localtry_lock(lock)
+
+/**
+ * localtry_lock_irq - Acquire a per CPU local lock and disable interrupts
+ * @lock:      The lock variable
+ */
+#define localtry_lock_irq(lock)                __localtry_lock_irq(lock)
+
+/**
+ * localtry_lock_irqsave - Acquire a per CPU local lock, save and disable
+ *                      interrupts
+ * @lock:      The lock variable
+ * @flags:     Storage for interrupt flags
+ */
+#define localtry_lock_irqsave(lock, flags)                             \
+       __localtry_lock_irqsave(lock, flags)
+
+/**
+ * localtry_trylock_irqsave - Try to acquire a per CPU local lock, save and 
disable
+ *                           interrupts if acquired
+ * @lock:      The lock variable
+ * @flags:     Storage for interrupt flags
+ *
+ * The function can be used in any context such as NMI or HARDIRQ. Due to
+ * locking constrains it will _always_ fail to acquire the lock on PREEMPT_RT.
+ */
+#define localtry_trylock_irqsave(lock, flags)                          \
+       __localtry_trylock_irqsave(lock, flags)
+
+/**
+ * local_unlock - Release a per CPU local lock
+ * @lock:      The lock variable
+ */
+#define localtry_unlock(lock)          __localtry_unlock(lock)
+
+/**
+ * local_unlock_irq - Release a per CPU local lock and enable interrupts
+ * @lock:      The lock variable
+ */
+#define localtry_unlock_irq(lock)              __localtry_unlock_irq(lock)
+
+/**
+ * localtry_unlock_irqrestore - Release a per CPU local lock and restore
+ *                           interrupt flags
+ * @lock:      The lock variable
+ * @flags:      Interrupt flags to restore
+ */
+#define localtry_unlock_irqrestore(lock, flags)                        \
+       __localtry_unlock_irqrestore(lock, flags)
+
 DEFINE_GUARD(local_lock, local_lock_t __percpu*,
             local_lock(_T),
             local_unlock(_T))
diff --git a/include/linux/local_lock_internal.h 
b/include/linux/local_lock_internal.h
index 
8dd71fbbb6d2b6748969438c4642f7d970834871..c1369b300777d3ff3700cfd8bd4de8186124f036
 100644
--- a/include/linux/local_lock_internal.h
+++ b/include/linux/local_lock_internal.h
@@ -15,6 +15,11 @@ typedef struct {
 #endif
 } local_lock_t;
 
+typedef struct {
+       local_lock_t    llock;
+       unsigned int    acquired;
+} localtry_lock_t;
+
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # define LOCAL_LOCK_DEBUG_INIT(lockname)               \
        .dep_map = {                                    \
@@ -31,6 +36,13 @@ static inline void local_lock_acquire(local_lock_t *l)
        l->owner = current;
 }
 
+static inline void local_trylock_acquire(local_lock_t *l)
+{
+       lock_map_acquire_try(&l->dep_map);
+       DEBUG_LOCKS_WARN_ON(l->owner);
+       l->owner = current;
+}
+
 static inline void local_lock_release(local_lock_t *l)
 {
        DEBUG_LOCKS_WARN_ON(l->owner != current);
@@ -45,11 +57,13 @@ static inline void local_lock_debug_init(local_lock_t *l)
 #else /* CONFIG_DEBUG_LOCK_ALLOC */
 # define LOCAL_LOCK_DEBUG_INIT(lockname)
 static inline void local_lock_acquire(local_lock_t *l) { }
+static inline void local_trylock_acquire(local_lock_t *l) { }
 static inline void local_lock_release(local_lock_t *l) { }
 static inline void local_lock_debug_init(local_lock_t *l) { }
 #endif /* !CONFIG_DEBUG_LOCK_ALLOC */
 
 #define INIT_LOCAL_LOCK(lockname)      { LOCAL_LOCK_DEBUG_INIT(lockname) }
+#define INIT_LOCALTRY_LOCK(lockname)   { .llock = { 
LOCAL_LOCK_DEBUG_INIT(lockname.llock) }}
 
 #define __local_lock_init(lock)                                        \
 do {                                                           \
@@ -118,6 +132,86 @@ do {                                                       
        \
 #define __local_unlock_nested_bh(lock)                         \
        local_lock_release(this_cpu_ptr(lock))
 
+/* localtry_lock_t variants */
+
+#define __localtry_lock_init(lock)                             \
+do {                                                           \
+       __local_lock_init(&(lock)->llock);                      \
+       WRITE_ONCE(&(lock)->acquired, 0);                       \
+} while (0)
+
+#define __localtry_lock(lock)                                  \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               preempt_disable();                              \
+               lt = this_cpu_ptr(lock);                        \
+               local_lock_acquire(&lt->llock);                 \
+               WRITE_ONCE(lt->acquired, 1);                    \
+       } while (0)
+
+#define __localtry_lock_irq(lock)                              \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               local_irq_disable();                            \
+               lt = this_cpu_ptr(lock);                        \
+               local_lock_acquire(&lt->llock);                 \
+               WRITE_ONCE(lt->acquired, 1);                    \
+       } while (0)
+
+#define __localtry_lock_irqsave(lock, flags)                   \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               local_irq_save(flags);                          \
+               lt = this_cpu_ptr(lock);                        \
+               local_lock_acquire(&lt->llock);                 \
+               WRITE_ONCE(lt->acquired, 1);                    \
+       } while (0)
+
+#define __localtry_trylock_irqsave(lock, flags)                        \
+       ({                                                      \
+               localtry_lock_t *lt;                            \
+               bool _ret;                                      \
+                                                               \
+               local_irq_save(flags);                          \
+               lt = this_cpu_ptr(lock);                        \
+               if (!READ_ONCE(lt->acquired)) {                 \
+                       WRITE_ONCE(lt->acquired, 1);            \
+                       local_trylock_acquire(&lt->llock);      \
+                       _ret = true;                            \
+               } else {                                        \
+                       _ret = false;                           \
+                       local_irq_restore(flags);               \
+               }                                               \
+               _ret;                                           \
+       })
+
+#define __localtry_unlock(lock)                                        \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               lt = this_cpu_ptr(lock);                        \
+               WRITE_ONCE(lt->acquired, 0);                    \
+               local_lock_release(&lt->llock);                 \
+               preempt_enable();                               \
+       } while (0)
+
+#define __localtry_unlock_irq(lock)                            \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               lt = this_cpu_ptr(lock);                        \
+               WRITE_ONCE(lt->acquired, 0);                    \
+               local_lock_release(&lt->llock);                 \
+               local_irq_enable();                             \
+       } while (0)
+
+#define __localtry_unlock_irqrestore(lock, flags)              \
+       do {                                                    \
+               localtry_lock_t *lt;                            \
+               lt = this_cpu_ptr(lock);                        \
+               WRITE_ONCE(lt->acquired, 0);                    \
+               local_lock_release(&lt->llock);                 \
+               local_irq_restore(flags);                       \
+       } while (0)
+
 #else /* !CONFIG_PREEMPT_RT */
 
 /*
@@ -125,8 +219,10 @@ do {                                                       
        \
  * critical section while staying preemptible.
  */
 typedef spinlock_t local_lock_t;
+typedef spinlock_t localtry_lock_t;
 
 #define INIT_LOCAL_LOCK(lockname) __LOCAL_SPIN_LOCK_UNLOCKED((lockname))
+#define INIT_LOCALTRY_LOCK(lockname) INIT_LOCAL_LOCK(lockname)
 
 #define __local_lock_init(l)                                   \
        do {                                                    \
@@ -169,4 +265,31 @@ do {                                                       
        \
        spin_unlock(this_cpu_ptr((lock)));                      \
 } while (0)
 
+/* localtry_lock_t variants */
+
+#define __localtry_lock_init(lock)                     __local_lock_init(lock)
+#define __localtry_lock(lock)                          __local_lock(lock)
+#define __localtry_lock_irq(lock)                      __local_lock(lock)
+#define __localtry_lock_irqsave(lock, flags)           
__local_lock_irqsave(lock, flags)
+#define __localtry_unlock(lock)                                
__local_unlock(lock)
+#define __localtry_unlock_irq(lock)                    __local_unlock(lock)
+#define __localtry_unlock_irqrestore(lock, flags)      
__local_unlock_irqrestore(lock, flags)
+
+#define __localtry_trylock_irqsave(lock, flags)                        \
+       ({                                                      \
+               int __locked;                                   \
+                                                               \
+               typecheck(unsigned long, flags);                \
+               flags = 0;                                      \
+               if (in_nmi() | in_hardirq()) {                  \
+                       __locked = 0;                           \
+               } else {                                        \
+                       migrate_disable();                      \
+                       __locked = spin_trylock(this_cpu_ptr((lock)));  \
+                       if (!__locked)                          \
+                               migrate_enable();               \
+               }                                               \
+               __locked;                                       \
+       })
+
 #endif /* CONFIG_PREEMPT_RT */

-- 
2.48.1


Reply via email to