kernel - Refactor kern_mutex (mtx* functions)
authorMatthew Dillon <dillon@apollo.backplane.com>
Mon, 16 Mar 2015 01:48:15 +0000 (18:48 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Mon, 16 Mar 2015 01:48:15 +0000 (18:48 -0700)
* Refactor kern_mutex in order to support asynchronous lock requests,
  which hammer2 is going to need.  kern_mutex already supports abortable
  locks.

* Add callback fields to the mtx_link structure.

* Use the mtx_link structure for shared locks in addition to exclusive locks,
  allowing asynchronous callbacks for shared locks and exclusive locks.

* Make the locking flags more deterministic.

* Redo the typedefs to be more like hammer2.  Typedef the structures rather
  than pointers so the typedef names can be used for structural embedding.

sys/kern/kern_mutex.c
sys/sys/mutex.h
sys/sys/mutex2.h
sys/vfs/nfs/nfs.h
sys/vfs/nfs/nfs_socket.c
sys/vfs/nfs/nfs_syscalls.c

index 81020a4..2841086 100644 (file)
  * while the thread is running.  Mutexes can be held across blocking
  * conditions.
  *
+ * - Exclusive priority over shared to prevent SMP starvation.
+ * - locks can be aborted (async callback, if any, will be made w/ENOLCK).
+ * - locks can be asynchronous.
+ * - synchronous fast path if no blocking occurs (async callback is not
+ *   made in this case).
+ *
+ * Generally speaking any caller-supplied link state must be properly
+ * initialized before use.
+ *
  * Most of the support is in sys/mutex[2].h.  We mostly provide backoff
  * functions here.
  */
@@ -66,162 +75,159 @@ SYSCTL_QUAD(_kern, OID_AUTO, mtx_collision_count, CTLFLAG_RW,
 SYSCTL_QUAD(_kern, OID_AUTO, mtx_wakeup_count, CTLFLAG_RW,
            &mtx_wakeup_count, 0, "");
 
-static void mtx_chain_link(mtx_t mtx);
-static void mtx_delete_link(mtx_t mtx, mtx_link_t link);
+static int mtx_chain_link_ex(mtx_t *mtx, u_int olock);
+static int mtx_chain_link_sh(mtx_t *mtx, u_int olock, int addcount);
+static void mtx_delete_link(mtx_t *mtx, mtx_link_t *link);
 
 /*
- * Exclusive-lock a mutex, block until acquired.  Recursion is allowed.
+ * Exclusive-lock a mutex, block until acquired unless link is async.
+ * Recursion is allowed.
  *
- * Returns 0 on success, or the tsleep() return code on failure.
- * An error can only be returned if PCATCH is specified in the flags.
+ * Returns 0 on success, the tsleep() return code on failure, EINPROGRESS
+ * if async.  If immediately successful an async exclusive lock will return 0
+ * and not issue the async callback or link the link structure.  The caller
+ * must handle this case (typically this is an optimal code path).
+ *
+ * A tsleep() error can only be returned if PCATCH is specified in the flags.
  */
 static __inline int
-__mtx_lock_ex(mtx_t mtx, mtx_link_t link, const char *ident, int flags, int to)
+__mtx_lock_ex(mtx_t *mtx, mtx_link_t *link,
+             const char *ident, int flags, int to)
 {
+       thread_t td;
        u_int   lock;
        u_int   nlock;
        int     error;
+       int     isasync;
 
        for (;;) {
                lock = mtx->mtx_lock;
+               cpu_ccfence();
+
                if (lock == 0) {
                        nlock = MTX_EXCLUSIVE | 1;
                        if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
                                mtx->mtx_owner = curthread;
+                               link->state = MTX_LINK_ACQUIRED;
                                error = 0;
                                break;
                        }
-               } else if ((lock & MTX_EXCLUSIVE) &&
-                          mtx->mtx_owner == curthread) {
+                       continue;
+               }
+               if ((lock & MTX_EXCLUSIVE) && mtx->mtx_owner == curthread) {
                        KKASSERT((lock & MTX_MASK) != MTX_MASK);
                        nlock = lock + 1;
                        if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
+                               link->state = MTX_LINK_ACQUIRED;
                                error = 0;
                                break;
                        }
-               } else {
-                       /*
-                        * Clearing MTX_EXLINK in lock causes us to loop until
-                        * MTX_EXLINK is available.  However, to avoid
-                        * unnecessary cpu cache traffic we poll instead.
-                        *
-                        * Setting MTX_EXLINK in nlock causes us to loop until
-                        * we can acquire MTX_EXLINK.
-                        *
-                        * Also set MTX_EXWANTED coincident with EXLINK, if
-                        * not already set.
-                        */
-                       thread_t td;
-
-                       if (lock & MTX_EXLINK) {
-                               cpu_pause();
-                               ++mtx_collision_count;
-                               continue;
-                       }
-                       td = curthread;
-                       /*lock &= ~MTX_EXLINK;*/
-                       nlock = lock | MTX_EXWANTED | MTX_EXLINK;
-                       ++td->td_critcount;
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                               /*
-                                * Check for early abort
-                                */
-                               if (link->state == MTX_LINK_ABORTED) {
-                                       atomic_clear_int(&mtx->mtx_lock,
-                                                        MTX_EXLINK);
-                                       --td->td_critcount;
-                                       error = ENOLCK;
-                                       if (mtx->mtx_link == NULL) {
-                                               atomic_clear_int(&mtx->mtx_lock,
-                                                                MTX_EXWANTED);
-                                       }
-                                       break;
-                               }
-
-                               /*
-                                * Success.  Link in our structure then
-                                * release EXLINK and sleep.
-                                */
-                               link->owner = td;
-                               link->state = MTX_LINK_LINKED;
-                               if (mtx->mtx_link) {
-                                       link->next = mtx->mtx_link;
-                                       link->prev = link->next->prev;
-                                       link->next->prev = link;
-                                       link->prev->next = link;
-                               } else {
-                                       link->next = link;
-                                       link->prev = link;
-                                       mtx->mtx_link = link;
-                               }
-                               tsleep_interlock(link, 0);
-                               atomic_clear_int(&mtx->mtx_lock, MTX_EXLINK);
-                               --td->td_critcount;
-
-                               mycpu->gd_cnt.v_lock_name[0] = 'X';
-                               strncpy(mycpu->gd_cnt.v_lock_name + 1,
-                                       ident,
-                                       sizeof(mycpu->gd_cnt.v_lock_name) - 2);
-                               ++mycpu->gd_cnt.v_lock_colls;
-
-                               error = tsleep(link, flags | PINTERLOCKED,
-                                              ident, to);
-                               ++mtx_contention_count;
-
-                               /*
-                                * Normal unlink, we should own the exclusive
-                                * lock now.
-                                */
-                               if (link->state == MTX_LINK_LINKED)
-                                       mtx_delete_link(mtx, link);
-                               if (link->state == MTX_LINK_ACQUIRED) {
-                                       KKASSERT(mtx->mtx_owner == link->owner);
-                                       error = 0;
-                                       break;
-                               }
+                       continue;
+               }
 
-                               /*
-                                * Aborted lock (mtx_abort_ex called).
-                                */
-                               if (link->state == MTX_LINK_ABORTED) {
-                                       error = ENOLCK;
-                                       break;
-                               }
+               /*
+                * We need MTX_LINKSPIN to manipulate exlink or
+                * shlink.
+                *
+                * We must set MTX_EXWANTED with MTX_LINKSPIN to indicate
+                * pending shared requests.  It cannot be set as a separate
+                * operation prior to acquiring MTX_LINKSPIN.
+                *
+                * To avoid unnecessary cpu cache traffic we poll
+                * for collisions.  It is also possible that EXWANTED
+                * state failing the above test was spurious, so all the
+                * tests must be repeated if we cannot obtain LINKSPIN
+                * with the prior state tests intact (i.e. don't reload
+                * the (lock) variable here, for heaven's sake!).
+                */
+               if (lock & MTX_LINKSPIN) {
+                       cpu_pause();
+                       ++mtx_collision_count;
+                       continue;
+               }
+               td = curthread;
+               nlock = lock | MTX_EXWANTED | MTX_LINKSPIN;
+               ++td->td_critcount;
+               if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock) == 0) {
+                       --td->td_critcount;
+                       continue;
+               }
 
-                               /*
-                                * tsleep error, else retry.
-                                */
-                               if (error)
-                                       break;
+               /*
+                * Check for early abort.
+                */
+               if (link->state == MTX_LINK_ABORTED) {
+                       if (mtx->mtx_exlink == NULL) {
+                               atomic_clear_int(&mtx->mtx_lock,
+                                                MTX_LINKSPIN |
+                                                MTX_EXWANTED);
                        } else {
-                               --td->td_critcount;
+                               atomic_clear_int(&mtx->mtx_lock,
+                                                MTX_LINKSPIN);
                        }
+                       --td->td_critcount;
+                       link->state = MTX_LINK_IDLE;
+                       error = ENOLCK;
+                       break;
                }
-               ++mtx_collision_count;
+
+               /*
+                * Add our link to the exlink list and release LINKSPIN.
+                */
+               link->owner = td;
+               link->state = MTX_LINK_LINKED_EX;
+               if (mtx->mtx_exlink) {
+                       link->next = mtx->mtx_exlink;
+                       link->prev = link->next->prev;
+                       link->next->prev = link;
+                       link->prev->next = link;
+               } else {
+                       link->next = link;
+                       link->prev = link;
+                       mtx->mtx_exlink = link;
+               }
+               isasync = (link->callback != NULL);
+               atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN);
+               --td->td_critcount;
+
+               /* 
+                * If asynchronous lock request return without
+                * blocking, leave link structure linked.
+                */
+               if (isasync) {
+                       error = EINPROGRESS;
+                       break;
+               }
+
+               /*
+                * Wait for lock
+                */
+               error = mtx_wait_link(mtx, link, ident, flags, to);
+               break;
        }
        return (error);
 }
 
 int
-_mtx_lock_ex_link(mtx_t mtx, mtx_link_t link,
+_mtx_lock_ex_link(mtx_t *mtx, mtx_link_t *link,
                  const char *ident, int flags, int to)
 {
        return(__mtx_lock_ex(mtx, link, ident, flags, to));
 }
 
 int
-_mtx_lock_ex(mtx_t mtx, const char *ident, int flags, int to)
+_mtx_lock_ex(mtx_t *mtx, const char *ident, int flags, int to)
 {
-       struct mtx_link link;
+       mtx_link_t link;
 
        mtx_link_init(&link);
        return(__mtx_lock_ex(mtx, &link, ident, flags, to));
 }
 
 int
-_mtx_lock_ex_quick(mtx_t mtx, const char *ident)
+_mtx_lock_ex_quick(mtx_t *mtx, const char *ident)
 {
-       struct mtx_link link;
+       mtx_link_t link;
 
        mtx_link_init(&link);
        return(__mtx_lock_ex(mtx, &link, ident, 0, 0));
@@ -237,66 +243,152 @@ _mtx_lock_ex_quick(mtx_t mtx, const char *ident)
  *      do not have to chain the wakeup().
  */
 static __inline int
-__mtx_lock_sh(mtx_t mtx, const char *ident, int flags, int to)
+__mtx_lock_sh(mtx_t *mtx, mtx_link_t *link,
+             const char *ident, int flags, int to)
 {
+       thread_t td;
        u_int   lock;
        u_int   nlock;
        int     error;
+       int     isasync;
 
        for (;;) {
                lock = mtx->mtx_lock;
-               if ((lock & MTX_EXCLUSIVE) == 0) {
+               cpu_ccfence();
+
+               if (lock == 0) {
+                       nlock = 1;
+                       if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
+                               error = 0;
+                               link->state = MTX_LINK_ACQUIRED;
+                               break;
+                       }
+                       continue;
+               }
+               if ((lock & (MTX_EXCLUSIVE | MTX_EXWANTED)) == 0) {
                        KKASSERT((lock & MTX_MASK) != MTX_MASK);
                        nlock = lock + 1;
                        if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
                                error = 0;
+                               link->state = MTX_LINK_ACQUIRED;
                                break;
                        }
-               } else {
-                       nlock = lock | MTX_SHWANTED;
-                       tsleep_interlock(mtx, 0);
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
+                       continue;
+               }
 
-                               mycpu->gd_cnt.v_lock_name[0] = 'S';
-                               strncpy(mycpu->gd_cnt.v_lock_name + 1,
-                                       ident,
-                                       sizeof(mycpu->gd_cnt.v_lock_name) - 2);
-                               ++mycpu->gd_cnt.v_lock_colls;
-
-                               error = tsleep(mtx, flags | PINTERLOCKED,
-                                              ident, to);
-                               if (error)
-                                       break;
-                               ++mtx_contention_count;
-                               /* retry */
+               /*
+                * We need MTX_LINKSPIN to manipulate exlink or
+                * shlink.
+                *
+                * We must set MTX_SHWANTED with MTX_LINKSPIN to indicate
+                * pending shared requests.  It cannot be set as a separate
+                * operation prior to acquiring MTX_LINKSPIN.
+                *
+                * To avoid unnecessary cpu cache traffic we poll
+                * for collisions.  It is also possible that EXWANTED
+                * state failing the above test was spurious, so all the
+                * tests must be repeated if we cannot obtain LINKSPIN
+                * with the prior state tests intact (i.e. don't reload
+                * the (lock) variable here, for heaven's sake!).
+                */
+               if (lock & MTX_LINKSPIN) {
+                       cpu_pause();
+                       ++mtx_collision_count;
+                       continue;
+               }
+               td = curthread;
+               nlock = lock | MTX_SHWANTED | MTX_LINKSPIN;
+               ++td->td_critcount;
+               if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock) == 0) {
+                       --td->td_critcount;
+                       continue;
+               }
+
+               /*
+                * Check for early abort.
+                */
+               if (link->state == MTX_LINK_ABORTED) {
+                       if (mtx->mtx_exlink == NULL) {
+                               atomic_clear_int(&mtx->mtx_lock,
+                                                MTX_LINKSPIN |
+                                                MTX_SHWANTED);
                        } else {
-                               crit_enter();
-                               tsleep_remove(curthread);
-                               crit_exit();
+                               atomic_clear_int(&mtx->mtx_lock,
+                                                MTX_LINKSPIN);
                        }
+                       --td->td_critcount;
+                       link->state = MTX_LINK_IDLE;
+                       error = ENOLCK;
+                       break;
                }
-               ++mtx_collision_count;
+
+               /*
+                * Add our link to the exlink list and release LINKSPIN.
+                */
+               link->owner = td;
+               link->state = MTX_LINK_LINKED_SH;
+               if (mtx->mtx_shlink) {
+                       link->next = mtx->mtx_shlink;
+                       link->prev = link->next->prev;
+                       link->next->prev = link;
+                       link->prev->next = link;
+               } else {
+                       link->next = link;
+                       link->prev = link;
+                       mtx->mtx_shlink = link;
+               }
+               isasync = (link->callback != NULL);
+               atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN);
+               --td->td_critcount;
+
+               /* 
+                * If asynchronous lock request return without
+                * blocking, leave link structure linked.
+                */
+               if (isasync) {
+                       error = EINPROGRESS;
+                       break;
+               }
+
+               /*
+                * Wait for lock
+                */
+               error = mtx_wait_link(mtx, link, ident, flags, to);
+               break;
        }
        return (error);
 }
 
 int
-_mtx_lock_sh(mtx_t mtx, const char *ident, int flags, int to)
+_mtx_lock_sh_link(mtx_t *mtx, mtx_link_t *link,
+                 const char *ident, int flags, int to)
+{
+       return(__mtx_lock_sh(mtx, link, ident, flags, to));
+}
+
+int
+_mtx_lock_sh(mtx_t *mtx, const char *ident, int flags, int to)
 {
-       return (__mtx_lock_sh(mtx, ident, flags, to));
+       mtx_link_t link;
+
+       mtx_link_init(&link);
+       return(__mtx_lock_sh(mtx, &link, ident, flags, to));
 }
 
 int
-_mtx_lock_sh_quick(mtx_t mtx, const char *ident)
+_mtx_lock_sh_quick(mtx_t *mtx, const char *ident)
 {
-       return (__mtx_lock_sh(mtx, ident, 0, 0));
+       mtx_link_t link;
+
+       mtx_link_init(&link);
+       return(__mtx_lock_sh(mtx, &link, ident, 0, 0));
 }
 
 /*
  * Get an exclusive spinlock the hard way.
  */
 void
-_mtx_spinlock(mtx_t mtx)
+_mtx_spinlock(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
@@ -338,7 +430,7 @@ _mtx_spinlock(mtx_t mtx)
  * Returns 0 on success, EAGAIN on failure.
  */
 int
-_mtx_spinlock_try(mtx_t mtx)
+_mtx_spinlock_try(mtx_t *mtx)
 {
        globaldata_t gd = mycpu;
        u_int   lock;
@@ -375,7 +467,7 @@ _mtx_spinlock_try(mtx_t mtx)
 #if 0
 
 void
-_mtx_spinlock_sh(mtx_t mtx)
+_mtx_spinlock_sh(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
@@ -406,11 +498,11 @@ _mtx_spinlock_sh(mtx_t mtx)
 #endif
 
 int
-_mtx_lock_ex_try(mtx_t mtx)
+_mtx_lock_ex_try(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
-       int     error = 0;
+       int     error;
 
        for (;;) {
                lock = mtx->mtx_lock;
@@ -418,14 +510,17 @@ _mtx_lock_ex_try(mtx_t mtx)
                        nlock = MTX_EXCLUSIVE | 1;
                        if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
                                mtx->mtx_owner = curthread;
+                               error = 0;
                                break;
                        }
                } else if ((lock & MTX_EXCLUSIVE) &&
                           mtx->mtx_owner == curthread) {
                        KKASSERT((lock & MTX_MASK) != MTX_MASK);
                        nlock = lock + 1;
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
+                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
+                               error = 0;
                                break;
+                       }
                } else {
                        error = EAGAIN;
                        break;
@@ -437,7 +532,7 @@ _mtx_lock_ex_try(mtx_t mtx)
 }
 
 int
-_mtx_lock_sh_try(mtx_t mtx)
+_mtx_lock_sh_try(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
@@ -468,25 +563,36 @@ _mtx_lock_sh_try(mtx_t mtx)
  * The exclusive count is converted to a shared count.
  */
 void
-_mtx_downgrade(mtx_t mtx)
+_mtx_downgrade(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
 
        for (;;) {
                lock = mtx->mtx_lock;
+               cpu_ccfence();
+
+               /*
+                * NOP if already shared.
+                */
                if ((lock & MTX_EXCLUSIVE) == 0) {
                        KKASSERT((lock & MTX_MASK) > 0);
                        break;
                }
-               KKASSERT(mtx->mtx_owner == curthread);
-               nlock = lock & ~(MTX_EXCLUSIVE | MTX_SHWANTED);
-               if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                       if (lock & MTX_SHWANTED) {
-                               wakeup(mtx);
-                               ++mtx_wakeup_count;
-                       }
-                       break;
+
+               /*
+                * Transfer count to shared.  Any additional pending shared
+                * waiters must be woken up.
+                */
+               if (lock & MTX_SHWANTED) {
+                       if (mtx_chain_link_sh(mtx, lock, 1))
+                               break;
+                       /* retry */
+               } else {
+                       nlock = lock & ~MTX_EXCLUSIVE;
+                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
+                               break;
+                       /* retry */
                }
                cpu_pause();
                ++mtx_collision_count;
@@ -505,7 +611,7 @@ _mtx_downgrade(mtx_t mtx)
  * Returns 0 on success, EDEADLK on failure.
  */
 int
-_mtx_upgrade_try(mtx_t mtx)
+_mtx_upgrade_try(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
@@ -536,179 +642,238 @@ _mtx_upgrade_try(mtx_t mtx)
 /*
  * Unlock a lock.  The caller must hold the lock either shared or exclusive.
  *
- * Any release which makes the lock available when others want an exclusive
- * lock causes us to chain the owner to the next exclusive lock instead of
- * releasing the lock.
+ * On the last release we handle any pending chains.
  */
 void
-_mtx_unlock(mtx_t mtx)
+_mtx_unlock(mtx_t *mtx)
 {
        u_int   lock;
        u_int   nlock;
 
        for (;;) {
                lock = mtx->mtx_lock;
-               nlock = lock & ~(MTX_SHWANTED | MTX_EXLINK);
+               cpu_ccfence();
 
-               if (nlock == 1) {
+               switch(lock) {
+               case MTX_EXCLUSIVE | 1:
                        /*
-                        * Last release, shared lock, no exclusive waiters.
+                        * Last release, exclusive lock.
+                        * No exclusive or shared requests pending.
                         */
-                       nlock = lock & MTX_EXLINK;
+                       mtx->mtx_owner = NULL;
+                       nlock = 0;
                        if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
-                               break;
-               } else if (nlock == (MTX_EXCLUSIVE | 1)) {
+                               goto done;
+                       break;
+               case MTX_EXCLUSIVE | MTX_EXWANTED | 1:
+               case MTX_EXCLUSIVE | MTX_EXWANTED | MTX_SHWANTED | 1:
                        /*
-                        * Last release, exclusive lock, no exclusive waiters.
-                        * Wake up any shared waiters.
+                        * Last release, exclusive lock.
+                        * Exclusive requests pending.
+                        * Exclusive requests have priority over shared reqs.
                         */
-                       mtx->mtx_owner = NULL;
-                       nlock = lock & MTX_EXLINK;
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                               if (lock & MTX_SHWANTED) {
-                                       wakeup(mtx);
-                                       ++mtx_wakeup_count;
-                               }
-                               break;
-                       }
-               } else if (nlock == (MTX_EXWANTED | 1)) {
+                       if (mtx_chain_link_ex(mtx, lock))
+                               goto done;
+                       break;
+               case MTX_EXCLUSIVE | MTX_SHWANTED | 1:
                        /*
-                        * Last release, shared lock, with exclusive
-                        * waiters.
+                        * Last release, exclusive lock.
                         *
-                        * Wait for EXLINK to clear, then acquire it.
-                        * We could use the cmpset for this but polling
-                        * is better on the cpu caches.
-                        *
-                        * Acquire an exclusive lock leaving the lockcount
-                        * set to 1, and get EXLINK for access to mtx_link.
+                        * Shared requests are pending.  Transfer our count (1)
+                        * to the first shared request, wakeup all shared reqs.
                         */
-                       thread_t td;
-
-                       if (lock & MTX_EXLINK) {
-                               cpu_pause();
-                               ++mtx_collision_count;
-                               continue;
-                       }
-                       td = curthread;
-                       /*lock &= ~MTX_EXLINK;*/
-                       nlock |= MTX_EXLINK | MTX_EXCLUSIVE;
-                       nlock |= (lock & MTX_SHWANTED);
-                       ++td->td_critcount;
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                               mtx_chain_link(mtx);
-                               --td->td_critcount;
-                               break;
-                       }
-                       --td->td_critcount;
-               } else if (nlock == (MTX_EXCLUSIVE | MTX_EXWANTED | 1)) {
+                       if (mtx_chain_link_sh(mtx, lock, 0))
+                               goto done;
+                       break;
+               case 1:
+                       /*
+                        * Last release, shared lock.
+                        * No exclusive or shared requests pending.
+                        */
+                       nlock = 0;
+                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
+                               goto done;
+                       break;
+               case MTX_EXWANTED | 1:
+               case MTX_EXWANTED | MTX_SHWANTED | 1:
                        /*
-                        * Last release, exclusive lock, with exclusive
-                        * waiters.
+                        * Last release, shared lock.
                         *
-                        * leave the exclusive lock intact and the lockcount
-                        * set to 1, and get EXLINK for access to mtx_link.
+                        * Exclusive requests are pending.  Transfer our
+                        * count (1) to the next exclusive request.
+                        *
+                        * Exclusive requests have priority over shared reqs.
                         */
-                       thread_t td;
-
-                       if (lock & MTX_EXLINK) {
-                               cpu_pause();
-                               ++mtx_collision_count;
-                               continue;
-                       }
-                       td = curthread;
-                       /*lock &= ~MTX_EXLINK;*/
-                       nlock |= MTX_EXLINK;
-                       nlock |= (lock & MTX_SHWANTED);
-                       ++td->td_critcount;
-                       if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                               mtx_chain_link(mtx);
-                               --td->td_critcount;
+                       if (mtx_chain_link_ex(mtx, lock))
+                               goto done;
+                       break;
+               case MTX_SHWANTED | 1:
+                       /*
+                        * Last release, shared lock.
+                        * Shared requests pending.
+                        */
+                       if (mtx_chain_link_sh(mtx, lock, 0))
+                               goto done;
+                       break;
+               default:
+                       /*
+                        * We have to loop if this is the last release but
+                        * someone is fiddling with LINKSPIN.
+                        */
+                       if ((lock & MTX_MASK) == 1) {
+                               KKASSERT(lock & MTX_LINKSPIN);
                                break;
                        }
-                       --td->td_critcount;
-               } else {
+
                        /*
                         * Not the last release (shared or exclusive)
                         */
                        nlock = lock - 1;
                        KKASSERT((nlock & MTX_MASK) != MTX_MASK);
                        if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
-                               break;
+                               goto done;
+                       break;
                }
+               /* loop try again */
                cpu_pause();
                ++mtx_collision_count;
        }
+done:
+       ;
 }
 
 /*
- * Chain mtx_chain_link.  Called with the lock held exclusively with a
- * single ref count, and also with MTX_EXLINK held.
+ * Chain pending links.  Called on the last release of an exclusive or
+ * shared lock when the appropriate WANTED bit is set.  mtx_lock old state
+ * is passed in with the count left at 1, which we can inherit, and other
+ * bits which we must adjust in a single atomic operation.
+ *
+ * Return non-zero on success, 0 if caller needs to retry.
+ *
+ * NOTE: It's ok if MTX_EXWANTED is in an indeterminant state while we are
+ *      acquiring LINKSPIN as all other cases will also need to acquire
+ *      LINKSPIN when handling the EXWANTED case.
  */
-static void
-mtx_chain_link(mtx_t mtx)
+static int
+mtx_chain_link_ex(mtx_t *mtx, u_int olock)
 {
-       mtx_link_t link;
-       u_int   lock;
+       thread_t td = curthread;
+       mtx_link_t *link;
        u_int   nlock;
-       u_int   clock;  /* bits we own and want to clear */
 
-       /*
-        * Chain the exclusive lock to the next link.  The caller cleared
-        * SHWANTED so if there is no link we have to wake up any shared
-        * waiters.
-        */
-       clock = MTX_EXLINK;
-       if ((link = mtx->mtx_link) != NULL) {
-               KKASSERT(link->state == MTX_LINK_LINKED);
+       olock &= ~MTX_LINKSPIN;
+       nlock = olock | MTX_LINKSPIN | MTX_EXCLUSIVE;
+       ++td->td_critcount;
+       if (atomic_cmpset_int(&mtx->mtx_lock, olock, nlock)) {
+               link = mtx->mtx_exlink;
+               KKASSERT(link != NULL);
                if (link->next == link) {
-                       mtx->mtx_link = NULL;
-                       clock |= MTX_EXWANTED;
+                       mtx->mtx_exlink = NULL;
+                       nlock = MTX_LINKSPIN | MTX_EXWANTED;    /* to clear */
                } else {
-                       mtx->mtx_link = link->next;
+                       mtx->mtx_exlink = link->next;
                        link->next->prev = link->prev;
                        link->prev->next = link->next;
+                       nlock = MTX_LINKSPIN;                   /* to clear */
                }
-               link->state = MTX_LINK_ACQUIRED;
+               KKASSERT(link->state == MTX_LINK_LINKED_EX);
                mtx->mtx_owner = link->owner;
-       } else {
+               cpu_sfence();
+
                /*
-                * Chain was empty, release the exclusive lock's last count
-                * as well the bits shown.
+                * WARNING! The callback can only be safely
+                *          made with LINKSPIN still held
+                *          and in a critical section.
+                *
+                * WARNING! The link can go away after the
+                *          state is set, or after the
+                *          callback.
                 */
-               clock |= MTX_EXCLUSIVE | MTX_EXWANTED | MTX_SHWANTED | 1;
+               if (link->callback) {
+                       link->state = MTX_LINK_CALLEDBACK;
+                       link->callback(link, link->arg, 0);
+               } else {
+                       link->state = MTX_LINK_ACQUIRED;
+                       wakeup(link);
+               }
+               atomic_clear_int(&mtx->mtx_lock, nlock);
+               --td->td_critcount;
+               ++mtx_wakeup_count;
+               return 1;
        }
+       /* retry */
+       --td->td_critcount;
+       return 0;
+}
 
-       /*
-        * We have to uset cmpset here to deal with MTX_SHWANTED.  If
-        * we just clear the bits we can miss a wakeup or, worse,
-        * leave mtx_lock unlocked with MTX_SHWANTED still set.
-        */
-       for (;;) {
-               lock = mtx->mtx_lock;
-               nlock = lock & ~clock;
+/*
+ * Flush waiting shared locks.  The lock's prior state is passed in and must
+ * be adjusted atomically only if it matches.
+ *
+ * If addcount is 0, the count for the first shared lock in the chain is
+ * assumed to have already been accounted for.
+ *
+ * If addcount is 1, the count for the first shared lock in the chain has
+ * not yet been accounted for.
+ */
+static int
+mtx_chain_link_sh(mtx_t *mtx, u_int olock, int addcount)
+{
+       thread_t td = curthread;
+       mtx_link_t *link;
+       u_int   nlock;
+
+       olock &= ~MTX_LINKSPIN;
+       nlock = olock | MTX_LINKSPIN;
+       nlock &= ~MTX_EXCLUSIVE;
+       ++td->td_critcount;
+       if (atomic_cmpset_int(&mtx->mtx_lock, olock, nlock)) {
+               KKASSERT(mtx->mtx_shlink != NULL);
+               for (;;) {
+                       link = mtx->mtx_shlink;
+                       atomic_add_int(&mtx->mtx_lock, addcount);
+                       KKASSERT(link->state == MTX_LINK_LINKED_SH);
+                       if (link->next == link) {
+                               mtx->mtx_shlink = NULL;
+                               cpu_sfence();
 
-               if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
-                       if (link) {
-                               /*
-                                * Wakeup new exclusive holder.  Leave
-                                * SHWANTED intact.
-                                */
-                               wakeup(link);
-                       } else if (lock & MTX_SHWANTED) {
                                /*
-                                * Signal any shared waiters (and we also
-                                * clear SHWANTED).
+                                * WARNING! The callback can only be safely
+                                *          made with LINKSPIN still held
+                                *          and in a critical section.
+                                *
+                                * WARNING! The link can go away after the
+                                *          state is set, or after the
+                                *          callback.
                                 */
-                               mtx->mtx_owner = NULL;
-                               wakeup(mtx);
+                               if (link->callback) {
+                                       link->state = MTX_LINK_CALLEDBACK;
+                                       link->callback(link, link->arg, 0);
+                               } else {
+                                       link->state = MTX_LINK_ACQUIRED;
+                                       wakeup(link);
+                               }
                                ++mtx_wakeup_count;
+                               break;
                        }
-                       break;
+                       mtx->mtx_shlink = link->next;
+                       link->next->prev = link->prev;
+                       link->prev->next = link->next;
+                       cpu_sfence();
+                       link->state = MTX_LINK_ACQUIRED;
+                       /* link can go away */
+                       wakeup(link);
+                       ++mtx_wakeup_count;
+                       addcount = 1;
                }
-               cpu_pause();
-               ++mtx_collision_count;
+               atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN |
+                                                MTX_SHWANTED);
+               --td->td_critcount;
+               return 1;
        }
+       /* retry */
+       --td->td_critcount;
+       return 0;
 }
 
 /*
@@ -717,28 +882,27 @@ mtx_chain_link(mtx_t mtx)
  */
 static
 void
-mtx_delete_link(mtx_t mtx, mtx_link_t link)
+mtx_delete_link(mtx_t *mtx, mtx_link_t *link)
 {
        thread_t td = curthread;
        u_int   lock;
        u_int   nlock;
 
        /*
-        * Acquire MTX_EXLINK.
+        * Acquire MTX_LINKSPIN.
         *
-        * Do not use cmpxchg to wait for EXLINK to clear as this might
+        * Do not use cmpxchg to wait for LINKSPIN to clear as this might
         * result in too much cpu cache traffic.
         */
        ++td->td_critcount;
        for (;;) {
                lock = mtx->mtx_lock;
-               if (lock & MTX_EXLINK) {
+               if (lock & MTX_LINKSPIN) {
                        cpu_pause();
                        ++mtx_collision_count;
                        continue;
                }
-               /* lock &= ~MTX_EXLINK; */
-               nlock = lock | MTX_EXLINK;
+               nlock = lock | MTX_LINKSPIN;
                if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
                        break;
                cpu_pause();
@@ -746,48 +910,147 @@ mtx_delete_link(mtx_t mtx, mtx_link_t link)
        }
 
        /*
-        * Delete the link and release EXLINK.
+        * Delete the link and release LINKSPIN.
         */
-       if (link->state == MTX_LINK_LINKED) {
+       nlock = MTX_LINKSPIN;   /* to clear */
+
+       switch(link->state) {
+       case MTX_LINK_LINKED_EX:
+               if (link->next == link) {
+                       mtx->mtx_exlink = NULL;
+                       nlock |= MTX_EXWANTED;  /* to clear */
+               } else {
+                       mtx->mtx_exlink = link->next;
+                       link->next->prev = link->prev;
+                       link->prev->next = link->next;
+               }
+               break;
+       case MTX_LINK_LINKED_SH:
                if (link->next == link) {
-                       mtx->mtx_link = NULL;
+                       mtx->mtx_shlink = NULL;
+                       nlock |= MTX_SHWANTED;  /* to clear */
                } else {
-                       mtx->mtx_link = link->next;
+                       mtx->mtx_shlink = link->next;
                        link->next->prev = link->prev;
                        link->prev->next = link->next;
                }
-               link->state = MTX_LINK_IDLE;
+               break;
+       default:
+               /* no change */
+               break;
        }
-       atomic_clear_int(&mtx->mtx_lock, MTX_EXLINK);
+       atomic_clear_int(&mtx->mtx_lock, nlock);
        --td->td_critcount;
 }
 
+/*
+ * Wait for async lock completion or abort.  Returns ENOLCK if an abort
+ * occurred.
+ */
+int
+mtx_wait_link(mtx_t *mtx, mtx_link_t *link,
+             const char *ident, int flags, int to)
+{
+       int error;
+
+       /*
+        * Sleep.  Handle false wakeups, interruptions, etc.
+        * The link may also have been aborted.
+        */
+       error = 0;
+       while (link->state & MTX_LINK_LINKED) {
+               tsleep_interlock(link, 0);
+               cpu_lfence();
+               if (link->state & MTX_LINK_LINKED) {
+                       ++mtx_contention_count;
+                       if (link->state & MTX_LINK_LINKED_SH)
+                               mycpu->gd_cnt.v_lock_name[0] = 'S';
+                       else
+                               mycpu->gd_cnt.v_lock_name[0] = 'X';
+                       strncpy(mycpu->gd_cnt.v_lock_name + 1,
+                               ident,
+                               sizeof(mycpu->gd_cnt.v_lock_name) - 2);
+                       ++mycpu->gd_cnt.v_lock_colls;
+
+                       error = tsleep(link, flags | PINTERLOCKED,
+                                      ident, to);
+                       if (error)
+                               break;
+               }
+       }
+
+       /*
+        * We are done, make sure the link structure is unlinked.
+        * It may still be on the list due to e.g. EINTR or
+        * EWOULDBLOCK.
+        *
+        * It is possible for the tsleep to race an ABORT and cause
+        * error to be 0.
+        *
+        * The tsleep() can be woken up for numerous reasons and error
+        * might be zero in situations where we intend to return an error.
+        *
+        * (This is the synchronous case so state cannot be CALLEDBACK)
+        */
+       switch(link->state) {
+       case MTX_LINK_ACQUIRED:
+       case MTX_LINK_CALLEDBACK:
+               error = 0;
+               break;
+       case MTX_LINK_ABORTED:
+               error = ENOLCK;
+               break;
+       case MTX_LINK_LINKED_EX:
+       case MTX_LINK_LINKED_SH:
+               mtx_delete_link(mtx, link);
+               /* fall through */
+       default:
+               if (error == 0)
+                       error = EWOULDBLOCK;
+               break;
+       }
+
+       /*
+        * Clear state on status returned.
+        */
+       link->state = MTX_LINK_IDLE;
+
+       return error;
+}
+
 /*
  * Abort a mutex locking operation, causing mtx_lock_ex_link() to
- * return ENOLCK.  This may be called at any time after the
- * mtx_link is initialized, including both before and after the call
- * to mtx_lock_ex_link().
+ * return ENOLCK.  This may be called at any time after the mtx_link
+ * is initialized or the status from a previous lock has been
+ * returned.  If called prior to the next (non-try) lock attempt, the
+ * next lock attempt using this link structure will abort instantly.
+ *
+ * Caller must still wait for the operation to complete, either from a
+ * blocking call that is still in progress or by calling mtx_wait_link().
+ *
+ * If an asynchronous lock request is possibly in-progress, the caller
+ * should call mtx_wait_link() synchronously.  Note that the asynchronous
+ * lock callback will NOT be called if a successful abort occurred. XXX
  */
 void
-mtx_abort_ex_link(mtx_t mtx, mtx_link_t link)
+mtx_abort_link(mtx_t *mtx, mtx_link_t *link)
 {
        thread_t td = curthread;
        u_int   lock;
        u_int   nlock;
 
        /*
-        * Acquire MTX_EXLINK
+        * Acquire MTX_LINKSPIN
         */
        ++td->td_critcount;
        for (;;) {
                lock = mtx->mtx_lock;
-               if (lock & MTX_EXLINK) {
+               if (lock & MTX_LINKSPIN) {
                        cpu_pause();
                        ++mtx_collision_count;
                        continue;
                }
-               /* lock &= ~MTX_EXLINK; */
-               nlock = lock | MTX_EXLINK;
+               nlock = lock | MTX_LINKSPIN;
                if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
                        break;
                cpu_pause();
@@ -795,8 +1058,12 @@ mtx_abort_ex_link(mtx_t mtx, mtx_link_t link)
        }
 
        /*
-        * Do the abort
+        * Do the abort.
+        *
+        * WARNING! Link structure can disappear once link->state is set.
         */
+       nlock = MTX_LINKSPIN;   /* to clear */
+
        switch(link->state) {
        case MTX_LINK_IDLE:
                /*
@@ -804,21 +1071,72 @@ mtx_abort_ex_link(mtx_t mtx, mtx_link_t link)
                 */
                link->state = MTX_LINK_ABORTED;
                break;
-       case MTX_LINK_LINKED:
+       case MTX_LINK_LINKED_EX:
                /*
-                * de-link, mark aborted, and wakeup the thread.
+                * de-link, mark aborted, and potentially wakeup the thread
+                * or issue the callback.
                 */
                if (link->next == link) {
-                       mtx->mtx_link = NULL;
+                       if (mtx->mtx_exlink == link) {
+                               mtx->mtx_exlink = NULL;
+                               nlock |= MTX_EXWANTED;  /* to clear */
+                       }
                } else {
-                       mtx->mtx_link = link->next;
+                       if (mtx->mtx_exlink == link)
+                               mtx->mtx_exlink = link->next;
                        link->next->prev = link->prev;
                        link->prev->next = link->next;
                }
-               link->state = MTX_LINK_ABORTED;
-               wakeup(link);
+
+               /*
+                * When aborting the async callback is still made.  We must
+                * not set the link status to ABORTED in the callback case
+                * since there is nothing else to clear its status if the
+                * link is reused.
+                */
+               if (link->callback) {
+                       link->state = MTX_LINK_CALLEDBACK;
+                       link->callback(link, link->arg, ENOLCK);
+               } else {
+                       link->state = MTX_LINK_ABORTED;
+                       wakeup(link);
+               }
+               ++mtx_wakeup_count;
+               break;
+       case MTX_LINK_LINKED_SH:
+               /*
+                * de-link, mark aborted, and potentially wakeup the thread
+                * or issue the callback.
+                */
+               if (link->next == link) {
+                       if (mtx->mtx_shlink == link) {
+                               mtx->mtx_shlink = NULL;
+                               nlock |= MTX_SHWANTED;  /* to clear */
+                       }
+               } else {
+                       if (mtx->mtx_shlink == link)
+                               mtx->mtx_shlink = link->next;
+                       link->next->prev = link->prev;
+                       link->prev->next = link->next;
+               }
+
+               /*
+                * When aborting the async callback is still made.  We must
+                * not set the link status to ABORTED in the callback case
+                * since there is nothing else to clear its status if the
+                * link is reused.
+                */
+               if (link->callback) {
+                       link->state = MTX_LINK_CALLEDBACK;
+                       link->callback(link, link->arg, ENOLCK);
+               } else {
+                       link->state = MTX_LINK_ABORTED;
+                       wakeup(link);
+               }
+               ++mtx_wakeup_count;
                break;
        case MTX_LINK_ACQUIRED:
+       case MTX_LINK_CALLEDBACK:
                /*
                 * Too late, the lock was acquired.  Let it complete.
                 */
@@ -829,6 +1147,6 @@ mtx_abort_ex_link(mtx_t mtx, mtx_link_t link)
                 */
                break;
        }
-       atomic_clear_int(&mtx->mtx_lock, MTX_EXLINK);
+       atomic_clear_int(&mtx->mtx_lock, nlock);
        --td->td_critcount;
 }
index db8fd45..3f5b32b 100644 (file)
@@ -57,37 +57,42 @@ struct mtx_link {
        struct mtx_link *prev;
        struct thread   *owner;
        int             state;
+       void            (*callback)(struct mtx_link *, void *arg, int error);
+       void            *arg;
 };
 
-typedef struct mtx_link        *mtx_link_t;
+typedef struct mtx_link        mtx_link_t;
 
 struct mtx {
        volatile u_int  mtx_lock;
-       int             mtx_refs;
+       int             mtx_reserved01; /* future use & struct alignmnent */
        struct thread   *mtx_owner;
-       mtx_link_t      mtx_link;
+       mtx_link_t      *mtx_exlink;
+       mtx_link_t      *mtx_shlink;
 } __cachealign;
 
-typedef struct mtx *mtx_t;
+typedef struct mtx mtx_t;
 
-#define MTX_INITIALIZER        { .mtx_lock = 0, .mtx_refs = 0, \
-                         .mtx_owner = NULL, .mtx_link = NULL }
+#define MTX_INITIALIZER        { .mtx_lock = 0, .mtx_owner = NULL, \
+                         .mtx_exlink = NULL, .mtx_shlink = NULL }
 
 #define MTX_EXCLUSIVE  0x80000000
 #define MTX_SHWANTED   0x40000000
 #define MTX_EXWANTED   0x20000000
-#define MTX_EXLINK     0x10000000
+#define MTX_LINKSPIN   0x10000000
 #define MTX_MASK       0x0FFFFFFF
 
-#define MTX_PCATCH     0x00000001
-
 #define MTX_OWNER_NONE NULL
 #define MTX_OWNER_ANON ((struct thread *)-2)
 
 #define MTX_LINK_IDLE          0
-#define MTX_LINK_ABORTED       -1
-#define MTX_LINK_LINKED                1
-#define MTX_LINK_ACQUIRED      2
+#define MTX_LINK_LINKED_SH     (MTX_LINK_LINKED | 1)
+#define MTX_LINK_LINKED_EX     (MTX_LINK_LINKED | 2)
+#define MTX_LINK_ACQUIRED      3
+#define MTX_LINK_CALLEDBACK    4
+#define MTX_LINK_ABORTED       5
+
+#define MTX_LINK_LINKED                0x1000
 
 #endif
 
@@ -96,19 +101,24 @@ typedef struct mtx *mtx_t;
  */
 #ifdef _KERNEL
 
-int    _mtx_lock_ex_link(mtx_t mtx, mtx_link_t link, const char *ident, int flags, int to);
-int    _mtx_lock_ex(mtx_t mtx, const char *ident, int flags, int to);
-int    _mtx_lock_sh(mtx_t mtx, const char *ident, int flags, int to);
-int    _mtx_lock_ex_quick(mtx_t mtx, const char *ident);
-int    _mtx_lock_sh_quick(mtx_t mtx, const char *ident);
-void   _mtx_spinlock(mtx_t mtx);
-int    _mtx_spinlock_try(mtx_t mtx);
-int    _mtx_lock_ex_try(mtx_t mtx);
-int    _mtx_lock_sh_try(mtx_t mtx);
-void   _mtx_downgrade(mtx_t mtx);
-int    _mtx_upgrade_try(mtx_t mtx);
-void   _mtx_unlock(mtx_t mtx);
-void   mtx_abort_ex_link(mtx_t mtx, mtx_link_t link);
+int    _mtx_lock_ex_link(mtx_t *mtx, mtx_link_t *link,
+                               const char *ident, int flags, int to);
+int    _mtx_lock_ex(mtx_t *mtx, const char *ident, int flags, int to);
+int    _mtx_lock_sh_link(mtx_t *mtx, mtx_link_t *link,
+                               const char *ident, int flags, int to);
+int    _mtx_lock_sh(mtx_t *mtx, const char *ident, int flags, int to);
+int    _mtx_lock_ex_quick(mtx_t *mtx, const char *ident);
+int    _mtx_lock_sh_quick(mtx_t *mtx, const char *ident);
+void   _mtx_spinlock(mtx_t *mtx);
+int    _mtx_spinlock_try(mtx_t *mtx);
+int    _mtx_lock_ex_try(mtx_t *mtx);
+int    _mtx_lock_sh_try(mtx_t *mtx);
+void   _mtx_downgrade(mtx_t *mtx);
+int    _mtx_upgrade_try(mtx_t *mtx);
+void   _mtx_unlock(mtx_t *mtx);
+void   mtx_abort_link(mtx_t *mtx, mtx_link_t *link);
+int    mtx_wait_link(mtx_t *mtx, mtx_link_t *link,
+                               const char *ident, int flags, int to);
 
 #endif
 
index ada23be..016b464 100644 (file)
  * Initialize a new mutex, placing it in an unlocked state with no refs.
  */
 static __inline void
-mtx_init(mtx_t mtx)
+mtx_init(mtx_t *mtx)
 {
        mtx->mtx_lock = 0;
-       mtx->mtx_refs = 0;
        mtx->mtx_owner = NULL;
-       mtx->mtx_link = NULL;
+       mtx->mtx_exlink = NULL;
+       mtx->mtx_shlink = NULL;
 }
 
+/*
+ * Initialize a mtx link structure for deeper control over the mutex
+ * operation.
+ */
 static __inline void
-mtx_link_init(mtx_link_t link)
+mtx_link_init(mtx_link_t *link)
 {
        link->state = MTX_LINK_IDLE;
+       link->callback = NULL;
+       link->arg = NULL;
+}
+
+/*
+ * A link structure initialized this way causes mutex operations to not block,
+ * caller must specify a callback.  Caller may still abort the mutex via
+ * the link.
+ */
+static __inline void
+mtx_link_init_async(mtx_link_t *link,
+                   void (*callback)(mtx_link_t *link, void *arg, int error),
+                   void *arg)
+{
+       link->state = MTX_LINK_IDLE;
+       link->callback = callback;
+       link->arg = arg;
 }
 
 /*
  * Deinitialize a mutex
  */
 static __inline void
-mtx_uninit(mtx_t mtx)
+mtx_uninit(mtx_t *mtx)
 {
        /* empty */
 }
@@ -79,18 +100,20 @@ mtx_uninit(mtx_t mtx)
  *
  * This version of the function allows the mtx_link to be passed in, thus
  * giving the caller visibility for the link structure which is required
- * when calling mtx_abort_ex_link().
+ * when calling mtx_abort_ex_link() or when requesting an asynchronous lock.
  *
  * The mutex may be aborted at any time while the passed link structure
  * is valid.
  */
 static __inline int
-mtx_lock_ex_link(mtx_t mtx, struct mtx_link *link,
+mtx_lock_ex_link(mtx_t *mtx, mtx_link_t *link,
                  const char *ident, int flags, int to)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, MTX_EXCLUSIVE | 1) == 0)
                return(_mtx_lock_ex_link(mtx, link, ident, flags, to));
        mtx->mtx_owner = curthread;
+       link->state = MTX_LINK_ACQUIRED;
+
        return(0);
 }
 
@@ -99,7 +122,7 @@ mtx_lock_ex_link(mtx_t mtx, struct mtx_link *link,
  * allowed.  This is equivalent to mtx_lock_ex(mtx, "mtxex", 0, 0).
  */
 static __inline void
-mtx_lock(mtx_t mtx)
+mtx_lock(mtx_t *mtx)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, MTX_EXCLUSIVE | 1) == 0) {
                _mtx_lock_ex(mtx, "mtxex", 0, 0);
@@ -115,7 +138,7 @@ mtx_lock(mtx_t mtx)
  * An error can only be returned if PCATCH is specified in the flags.
  */
 static __inline int
-mtx_lock_ex(mtx_t mtx, const char *ident, int flags, int to)
+mtx_lock_ex(mtx_t *mtx, const char *ident, int flags, int to)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, MTX_EXCLUSIVE | 1) == 0)
                return(_mtx_lock_ex(mtx, ident, flags, to));
@@ -124,7 +147,7 @@ mtx_lock_ex(mtx_t mtx, const char *ident, int flags, int to)
 }
 
 static __inline int
-mtx_lock_ex_quick(mtx_t mtx, const char *ident)
+mtx_lock_ex_quick(mtx_t *mtx, const char *ident)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, MTX_EXCLUSIVE | 1) == 0)
                return(_mtx_lock_ex_quick(mtx, ident));
@@ -132,6 +155,16 @@ mtx_lock_ex_quick(mtx_t mtx, const char *ident)
        return(0);
 }
 
+static __inline int
+mtx_lock_sh_link(mtx_t *mtx, mtx_link_t *link,
+                const char *ident, int flags, int to)
+{
+       if (atomic_cmpset_int(&mtx->mtx_lock, 0, 1) == 0)
+               return(_mtx_lock_sh_link(mtx, link, ident, flags, to));
+       link->state = MTX_LINK_ACQUIRED;
+       return(0);
+}
+
 /*
  * Share-lock a mutex, block until acquired.  Recursion is allowed.
  *
@@ -139,7 +172,7 @@ mtx_lock_ex_quick(mtx_t mtx, const char *ident)
  * An error can only be returned if PCATCH is specified in the flags.
  */
 static __inline int
-mtx_lock_sh(mtx_t mtx, const char *ident, int flags, int to)
+mtx_lock_sh(mtx_t *mtx, const char *ident, int flags, int to)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, 1) == 0)
                return(_mtx_lock_sh(mtx, ident, flags, to));
@@ -147,7 +180,7 @@ mtx_lock_sh(mtx_t mtx, const char *ident, int flags, int to)
 }
 
 static __inline int
-mtx_lock_sh_quick(mtx_t mtx, const char *ident)
+mtx_lock_sh_quick(mtx_t *mtx, const char *ident)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, 1) == 0)
                return(_mtx_lock_sh_quick(mtx, ident));
@@ -159,7 +192,7 @@ mtx_lock_sh_quick(mtx_t mtx, const char *ident)
  * mtx_spinunlock().
  */
 static __inline void
-mtx_spinlock(mtx_t mtx)
+mtx_spinlock(mtx_t *mtx)
 {
        globaldata_t gd = mycpu;
 
@@ -183,7 +216,7 @@ mtx_spinlock(mtx_t mtx)
 }
 
 static __inline int
-mtx_spinlock_try(mtx_t mtx)
+mtx_spinlock_try(mtx_t *mtx)
 {
        globaldata_t gd = mycpu;
 
@@ -212,7 +245,7 @@ mtx_spinlock_try(mtx_t mtx)
  * EAGAIN on failure.
  */
 static __inline int
-mtx_lock_ex_try(mtx_t mtx)
+mtx_lock_ex_try(mtx_t *mtx)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, MTX_EXCLUSIVE | 1) == 0)
                return (_mtx_lock_ex_try(mtx));
@@ -225,7 +258,7 @@ mtx_lock_ex_try(mtx_t mtx)
  * EAGAIN on failure.
  */
 static __inline int
-mtx_lock_sh_try(mtx_t mtx)
+mtx_lock_sh_try(mtx_t *mtx)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 0, 1) == 0)
                return (_mtx_lock_sh_try(mtx));
@@ -240,7 +273,7 @@ mtx_lock_sh_try(mtx_t mtx)
  * The exclusive count is converted to a shared count.
  */
 static __inline void
-mtx_downgrade(mtx_t mtx)
+mtx_downgrade(mtx_t *mtx)
 {
        mtx->mtx_owner = NULL;
        if (atomic_cmpset_int(&mtx->mtx_lock, MTX_EXCLUSIVE | 1, 0) == 0)
@@ -259,7 +292,7 @@ mtx_downgrade(mtx_t mtx)
  * Returns 0 on success, EDEADLK on failure.
  */
 static __inline int
-mtx_upgrade_try(mtx_t mtx)
+mtx_upgrade_try(mtx_t *mtx)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 1, MTX_EXCLUSIVE | 1))
                return(0);
@@ -278,7 +311,7 @@ mtx_upgrade_try(mtx_t mtx)
  *      or mtx_spinlock() to lock it should also use mtx_unlock() to unlock.
  */
 static __inline void
-mtx_unlock(mtx_t mtx)
+mtx_unlock(mtx_t *mtx)
 {
        u_int lock = mtx->mtx_lock;
 
@@ -295,7 +328,7 @@ mtx_unlock(mtx_t mtx)
 }
 
 static __inline void
-mtx_unlock_ex(mtx_t mtx)
+mtx_unlock_ex(mtx_t *mtx)
 {
        u_int lock = mtx->mtx_lock;
 
@@ -309,7 +342,7 @@ mtx_unlock_ex(mtx_t mtx)
 }
 
 static __inline void
-mtx_unlock_sh(mtx_t mtx)
+mtx_unlock_sh(mtx_t *mtx)
 {
        if (atomic_cmpset_int(&mtx->mtx_lock, 1, 0) == 0)
                _mtx_unlock(mtx);
@@ -319,7 +352,7 @@ mtx_unlock_sh(mtx_t mtx)
  * NOTE: spinlocks are exclusive-only
  */
 static __inline void
-mtx_spinunlock(mtx_t mtx)
+mtx_spinunlock(mtx_t *mtx)
 {
        globaldata_t gd = mycpu;
 
@@ -335,7 +368,7 @@ mtx_spinunlock(mtx_t mtx)
  * anyone, including the owner.
  */
 static __inline int
-mtx_islocked(mtx_t mtx)
+mtx_islocked(mtx_t *mtx)
 {
        return(mtx->mtx_lock != 0);
 }
@@ -347,7 +380,7 @@ mtx_islocked(mtx_t mtx)
  * The mutex may in an unlocked or shared lock state.
  */
 static __inline int
-mtx_islocked_ex(mtx_t mtx)
+mtx_islocked_ex(mtx_t *mtx)
 {
        return((mtx->mtx_lock & MTX_EXCLUSIVE) != 0);
 }
@@ -356,7 +389,7 @@ mtx_islocked_ex(mtx_t mtx)
  * Return TRUE (non-zero) if the mutex is not locked.
  */
 static __inline int
-mtx_notlocked(mtx_t mtx)
+mtx_notlocked(mtx_t *mtx)
 {
        return(mtx->mtx_lock == 0);
 }
@@ -366,7 +399,7 @@ mtx_notlocked(mtx_t mtx)
  * The mutex may in an unlocked or shared lock state.
  */
 static __inline int
-mtx_notlocked_ex(mtx_t mtx)
+mtx_notlocked_ex(mtx_t *mtx)
 {
        return((mtx->mtx_lock & MTX_EXCLUSIVE) != 0);
 }
@@ -376,7 +409,7 @@ mtx_notlocked_ex(mtx_t mtx)
  * the caller.
  */
 static __inline int
-mtx_owned(mtx_t mtx)
+mtx_owned(mtx_t *mtx)
 {
        return((mtx->mtx_lock & MTX_EXCLUSIVE) && mtx->mtx_owner == curthread);
 }
@@ -386,7 +419,7 @@ mtx_owned(mtx_t mtx)
  * the caller.
  */
 static __inline int
-mtx_notowned(mtx_t mtx)
+mtx_notowned(mtx_t *mtx)
 {
        return((mtx->mtx_lock & MTX_EXCLUSIVE) == 0 ||
               mtx->mtx_owner != curthread);
@@ -400,30 +433,9 @@ mtx_notowned(mtx_t mtx)
  *      caller the lock count for the other owner is still returned.
  */
 static __inline int
-mtx_lockrefs(mtx_t mtx)
+mtx_lockrefs(mtx_t *mtx)
 {
        return(mtx->mtx_lock & MTX_MASK);
 }
 
-/*
- * Bump the lock's ref count.  This field is independent of the lock.
- */
-static __inline void
-mtx_hold(mtx_t mtx)
-{
-       atomic_add_acq_int(&mtx->mtx_refs, 1);
-}
-
-/*
- * Drop the lock's ref count.  This field is independent of the lock.
- *
- * Returns the previous ref count, interlocked so testing against
- * 1 means you won the 1->0 transition
- */
-static __inline int
-mtx_drop(mtx_t mtx)
-{
-       return (atomic_fetchadd_int(&mtx->mtx_refs, -1));
-}
-
 #endif
index fcb8bdf..b66856f 100644 (file)
@@ -350,7 +350,7 @@ struct nfsm_info;
 struct nfsreq {
        TAILQ_ENTRY(nfsreq) r_chain;
        struct nfsm_info *r_info;
-       struct mtx_link r_link;
+       mtx_link_t      r_link;
        struct mbuf     *r_mreq;
        struct mbuf     *r_mrep;
        struct mbuf     *r_md;
index 1883d9d..fe22210 100644 (file)
@@ -422,9 +422,12 @@ nfs_disconnect(struct nfsmount *nmp)
 void
 nfs_safedisconnect(struct nfsmount *nmp)
 {
-       nfs_rcvlock(nmp, NULL);
+       int error;
+
+       error = nfs_rcvlock(nmp, NULL);
        nfs_disconnect(nmp);
-       nfs_rcvunlock(nmp);
+       if (error == 0)
+               nfs_rcvunlock(nmp);
 }
 
 /*
@@ -2047,7 +2050,7 @@ nfs_hardterm(struct nfsreq *rep, int islocked)
                                nfssvc_iod_writer_wakeup(nmp);
                        }
                }
-               mtx_abort_ex_link(&nmp->nm_rxlock, &rep->r_link);
+               mtx_abort_link(&nmp->nm_rxlock, &rep->r_link);
        }
 }
 
@@ -2092,7 +2095,7 @@ nfs_sigintr(struct nfsmount *nmp, struct nfsreq *rep, struct thread *td)
 int
 nfs_sndlock(struct nfsmount *nmp, struct nfsreq *rep)
 {
-       mtx_t mtx = &nmp->nm_txlock;
+       mtx_t *mtx = &nmp->nm_txlock;
        struct thread *td;
        int slptimeo;
        int slpflag;
@@ -2143,7 +2146,7 @@ nfs_sndunlock(struct nfsmount *nmp)
 static int
 nfs_rcvlock(struct nfsmount *nmp, struct nfsreq *rep)
 {
-       mtx_t mtx = &nmp->nm_rxlock;
+       mtx_t *mtx = &nmp->nm_rxlock;
        int slpflag;
        int slptimeo;
        int error;
index 459b3b9..1af18f8 100644 (file)
@@ -920,7 +920,7 @@ nfsrv_slpref(struct nfssvc_sock *slp)
 int
 nfs_slplock(struct nfssvc_sock *slp, int wait)
 {
-       mtx_t mtx = &slp->ns_solock;
+       mtx_t *mtx = &slp->ns_solock;
 
        if (wait) {
                mtx_lock_ex(mtx, "nfsslplck", 0, 0);
@@ -938,7 +938,7 @@ nfs_slplock(struct nfssvc_sock *slp, int wait)
 void
 nfs_slpunlock(struct nfssvc_sock *slp)
 {
-       mtx_t mtx = &slp->ns_solock;
+       mtx_t *mtx = &slp->ns_solock;
 
        mtx_unlock(mtx);
 }