[PARISC] Improve rwlock implementation
authorMatthew Wilcox <matthew@wil.cx>
Sat, 2 Sep 2006 13:54:58 +0000 (07:54 -0600)
committerMatthew Wilcox <willy@parisc-linux.org>
Wed, 4 Oct 2006 12:47:25 +0000 (06:47 -0600)
Rewrite rwlock implementation to avoid various deadlocks in the current
scheme.

Signed-off-by: Matthew Wilcox <matthew@wil.cx>
Signed-off-by: Kyle McMartin <kyle@parisc-linux.org>
include/asm-parisc/spinlock.h

index e182553..8980a87 100644 (file)
@@ -56,50 +56,70 @@ static inline int __raw_spin_trylock(raw_spinlock_t *x)
 }
 
 /*
- * Read-write spinlocks, allowing multiple readers
- * but only one writer.
+ * Read-write spinlocks, allowing multiple readers but only one writer.
+ * The spinlock is held by the writer, preventing any readers or other
+ * writers from grabbing the rwlock.  Readers use the lock to serialise their
+ * access to the counter (which records how many readers currently hold the
+ * lock).  Linux rwlocks are unfair to writers; they can be starved for
+ * an indefinite time by readers.  They can also be taken in interrupt context,
+ * so we have to disable interrupts when acquiring the spin lock to be sure
+ * that an interrupting reader doesn't get an inconsistent view of the lock.
  */
 
-#define __raw_read_trylock(lock) generic__raw_read_trylock(lock)
-
-/* read_lock, read_unlock are pretty straightforward.  Of course it somehow
- * sucks we end up saving/restoring flags twice for read_lock_irqsave aso. */
-
 static  __inline__ void __raw_read_lock(raw_rwlock_t *rw)
 {
+       unsigned long flags;
+       local_irq_save(flags);
        __raw_spin_lock(&rw->lock);
-
        rw->counter++;
-
        __raw_spin_unlock(&rw->lock);
+       local_irq_restore(flags);
 }
 
 static  __inline__ void __raw_read_unlock(raw_rwlock_t *rw)
 {
+       unsigned long flags;
+       local_irq_save(flags);
        __raw_spin_lock(&rw->lock);
-
        rw->counter--;
-
        __raw_spin_unlock(&rw->lock);
+       local_irq_restore(flags);
 }
 
-/* write_lock is less trivial.  We optimistically grab the lock and check
- * if we surprised any readers.  If so we release the lock and wait till
- * they're all gone before trying again
- *
- * Also note that we don't use the _irqsave / _irqrestore suffixes here.
- * If we're called with interrupts enabled and we've got readers (or other
- * writers) in interrupt handlers someone fucked up and we'd dead-lock
- * sooner or later anyway.   prumpf */
+static __inline__ int __raw_read_trylock(raw_rwlock_t *rw)
+{
+       unsigned long flags;
+ retry:
+       local_irq_save(flags);
+       if (__raw_spin_trylock(&rw->lock)) {
+               rw->counter++;
+               __raw_spin_unlock(&rw->lock);
+               local_irq_restore(flags);
+               return 1;
+       }
+
+       local_irq_restore(flags);
+       /* If write-locked, we fail to acquire the lock */
+       if (rw->counter < 0)
+               return 0;
+
+       /* Wait until we have a realistic chance at the lock */
+       while (__raw_spin_is_locked(&rw->lock) && rw->counter >= 0)
+               cpu_relax();
+
+       goto retry;
+}
 
-static  __inline__ void __raw_write_lock(raw_rwlock_t *rw)
+static __inline__ void __raw_write_lock(raw_rwlock_t *rw)
 {
+       unsigned long flags;
 retry:
+       local_irq_save(flags);
        __raw_spin_lock(&rw->lock);
 
-       if(rw->counter != 0) {
-               /* this basically never happens */
+       if (rw->counter != 0) {
                __raw_spin_unlock(&rw->lock);
+               local_irq_restore(flags);
 
                while (rw->counter != 0)
                        cpu_relax();
@@ -107,31 +127,35 @@ retry:
                goto retry;
        }
 
-       /* got it.  now leave without unlocking */
-       rw->counter = -1; /* remember we are locked */
+       rw->counter = -1; /* mark as write-locked */
+       mb();
+       local_irq_restore(flags);
 }
 
-/* write_unlock is absolutely trivial - we don't have to wait for anything */
-
-static  __inline__ void __raw_write_unlock(raw_rwlock_t *rw)
+static __inline__ void __raw_write_unlock(raw_rwlock_t *rw)
 {
        rw->counter = 0;
        __raw_spin_unlock(&rw->lock);
 }
 
-static  __inline__ int __raw_write_trylock(raw_rwlock_t *rw)
+static __inline__ int __raw_write_trylock(raw_rwlock_t *rw)
 {
-       __raw_spin_lock(&rw->lock);
-       if (rw->counter != 0) {
-               /* this basically never happens */
-               __raw_spin_unlock(&rw->lock);
-
-               return 0;
+       unsigned long flags;
+       int result = 0;
+
+       local_irq_save(flags);
+       if (__raw_spin_trylock(&rw->lock)) {
+               if (rw->counter == 0) {
+                       rw->counter = -1;
+                       result = 1;
+               } else {
+                       /* Read-locked.  Oh well. */
+                       __raw_spin_unlock(&rw->lock);
+               }
        }
+       local_irq_restore(flags);
 
-       /* got it.  now leave without unlocking */
-       rw->counter = -1; /* remember we are locked */
-       return 1;
+       return result;
 }
 
 /*