1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
  23  */
  24 
  25 /*
  26  * A Zero Reference Lock (ZRL) is a reference count that can lock out new
  27  * references only when the count is zero and only without waiting if the count
  28  * is not already zero. It is similar to a read-write lock in that it allows
  29  * multiple readers and only a single writer, but it does not allow a writer to
  30  * block while waiting for readers to exit, and therefore the question of
  31  * reader/writer priority is moot (no WRWANT bit). Since the equivalent of
  32  * rw_enter(&lock, RW_WRITER) is disallowed and only tryenter() is allowed, it
  33  * is perfectly safe for the same reader to acquire the same lock multiple
  34  * times. The fact that a ZRL is reentrant for readers (through multiple calls
  35  * to zrl_add()) makes it convenient for determining whether something is
  36  * actively referenced without the fuss of flagging lock ownership across
  37  * function calls.
  38  */
  39 #include <sys/zrlock.h>
  40 
  41 /*
  42  * A ZRL can be locked only while there are zero references, so ZRL_LOCKED is
  43  * treated as zero references.
  44  */
  45 #define ZRL_LOCKED      ((uint32_t)-1)
  46 #define ZRL_DESTROYED   -2
  47 
  48 void
  49 zrl_init(zrlock_t *zrl)
  50 {
  51         mutex_init(&zrl->zr_mtx, NULL, MUTEX_DEFAULT, NULL);
  52         zrl->zr_refcount = 0;
  53         cv_init(&zrl->zr_cv, NULL, CV_DEFAULT, NULL);
  54 #ifdef  ZFS_DEBUG
  55         zrl->zr_owner = NULL;
  56         zrl->zr_caller = NULL;
  57 #endif
  58 }
  59 
  60 void
  61 zrl_destroy(zrlock_t *zrl)
  62 {
  63         ASSERT(zrl->zr_refcount == 0);
  64 
  65         mutex_destroy(&zrl->zr_mtx);
  66         zrl->zr_refcount = ZRL_DESTROYED;
  67         cv_destroy(&zrl->zr_cv);
  68 }
  69 
  70 void
  71 #ifdef  ZFS_DEBUG
  72 zrl_add_debug(zrlock_t *zrl, const char *zc)
  73 #else
  74 zrl_add(zrlock_t *zrl)
  75 #endif
  76 {
  77         uint32_t n = (uint32_t)zrl->zr_refcount;
  78 
  79         while (1) {
  80                 while (n != ZRL_LOCKED) {
  81                         uint32_t cas = atomic_cas_32(
  82                             (uint32_t *)&zrl->zr_refcount, n, n + 1);
  83                         if (cas == n) {
  84                                 ASSERT((int32_t)n >= 0);
  85 #ifdef  ZFS_DEBUG
  86                                 if (zrl->zr_owner == curthread) {
  87                                         DTRACE_PROBE2(zrlock__reentry,
  88                                             zrlock_t *, zrl, uint32_t, n);
  89                                 }
  90                                 zrl->zr_owner = curthread;
  91                                 zrl->zr_caller = zc;
  92 #endif
  93                                 return;
  94                         }
  95                         n = cas;
  96                 }
  97                 mutex_enter(&zrl->zr_mtx);
  98                 while (zrl->zr_refcount == ZRL_LOCKED) {
  99                         cv_wait(&zrl->zr_cv, &zrl->zr_mtx);
 100                 }
 101                 mutex_exit(&zrl->zr_mtx);
 102         }
 103 }
 104 
 105 void
 106 zrl_remove(zrlock_t *zrl)
 107 {
 108         uint32_t n;
 109 
 110         n = atomic_dec_32_nv((uint32_t *)&zrl->zr_refcount);
 111         ASSERT((int32_t)n >= 0);
 112 #ifdef  ZFS_DEBUG
 113         if (zrl->zr_owner == curthread) {
 114                 zrl->zr_owner = NULL;
 115                 zrl->zr_caller = NULL;
 116         }
 117 #endif
 118 }
 119 
 120 int
 121 zrl_tryenter(zrlock_t *zrl)
 122 {
 123         uint32_t n = (uint32_t)zrl->zr_refcount;
 124 
 125         if (n == 0) {
 126                 uint32_t cas = atomic_cas_32(
 127                     (uint32_t *)&zrl->zr_refcount, 0, ZRL_LOCKED);
 128                 if (cas == 0) {
 129 #ifdef  ZFS_DEBUG
 130                         ASSERT(zrl->zr_owner == NULL);
 131                         zrl->zr_owner = curthread;
 132 #endif
 133                         return (1);
 134                 }
 135         }
 136 
 137         ASSERT((int32_t)n > ZRL_DESTROYED);
 138 
 139         return (0);
 140 }
 141 
 142 void
 143 zrl_exit(zrlock_t *zrl)
 144 {
 145         ASSERT(zrl->zr_refcount == ZRL_LOCKED);
 146 
 147         mutex_enter(&zrl->zr_mtx);
 148 #ifdef  ZFS_DEBUG
 149         ASSERT(zrl->zr_owner == curthread);
 150         zrl->zr_owner = NULL;
 151         membar_producer();      /* make sure the owner store happens first */
 152 #endif
 153         zrl->zr_refcount = 0;
 154         cv_broadcast(&zrl->zr_cv);
 155         mutex_exit(&zrl->zr_mtx);
 156 }
 157 
 158 int
 159 zrl_refcount(zrlock_t *zrl)
 160 {
 161         ASSERT(zrl->zr_refcount > ZRL_DESTROYED);
 162 
 163         int n = (int)zrl->zr_refcount;
 164         return (n <= 0 ? 0 : n);
 165 }
 166 
 167 int
 168 zrl_is_zero(zrlock_t *zrl)
 169 {
 170         ASSERT(zrl->zr_refcount > ZRL_DESTROYED);
 171 
 172         return (zrl->zr_refcount <= 0);
 173 }
 174 
 175 int
 176 zrl_is_locked(zrlock_t *zrl)
 177 {
 178         ASSERT(zrl->zr_refcount > ZRL_DESTROYED);
 179 
 180         return (zrl->zr_refcount == ZRL_LOCKED);
 181 }
 182 
 183 #ifdef  ZFS_DEBUG
 184 kthread_t *
 185 zrl_owner(zrlock_t *zrl)
 186 {
 187         return (zrl->zr_owner);
 188 }
 189 #endif