/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 1983,1984,1985,1986,1987,1988,1989 AT&T.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* A homegrown reader/writer lock implementation. It addresses
* two requirements not addressed by the system primitives. They
* are that the `enter" operation is optionally interruptible and
* that that they can be re`enter'ed by writers without deadlock.
*
* All of this was borrowed from NFS.
* See: uts/common/fs/nfs/nfs_subr.c
*
* XXX: Could we make this serve our needs instead?
* See: uts/common/os/rwstlock.c
* (and then use it for NFS too)
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>
/*
* Only can return non-zero if intr != 0.
*/
int
smbfs_rw_enter_sig(smbfs_rwlock_t *l, krw_t rw, int intr)
{
mutex_enter(&l->lock);
/*
* If this is a nested enter, then allow it. There
* must be as many exits as enters through.
*/
if (l->owner == curthread) {
/* lock is held for writing by current thread */
ASSERT(rw == RW_READER || rw == RW_WRITER);
l->count--;
} else if (rw == RW_READER) {
/*
* While there is a writer active or writers waiting,
* then wait for them to finish up and move on. Then,
* increment the count to indicate that a reader is
* active.
*/
while (l->count < 0 || l->waiters > 0) {
if (intr) {
klwp_t *lwp = ttolwp(curthread);
if (lwp != NULL)
lwp->lwp_nostop++;
if (!cv_wait_sig(&l->cv, &l->lock)) {
if (lwp != NULL)
lwp->lwp_nostop--;
mutex_exit(&l->lock);
return (EINTR);
}
if (lwp != NULL)
lwp->lwp_nostop--;
} else
cv_wait(&l->cv, &l->lock);
}
ASSERT(l->count < INT_MAX);
#ifdef SMBDEBUG
if ((l->count % 10000) == 9999)
cmn_err(CE_WARN, "smbfs_rw_enter_sig: count %d on"
"rwlock @ %p\n", l->count, (void *)&l);
#endif
l->count++;
} else {
ASSERT(rw == RW_WRITER);
/*
* While there are readers active or a writer
* active, then wait for all of the readers
* to finish or for the writer to finish.
* Then, set the owner field to curthread and
* decrement count to indicate that a writer
* is active.
*/
while (l->count > 0 || l->owner != NULL) {
l->waiters++;
if (intr) {
klwp_t *lwp = ttolwp(curthread);
if (lwp != NULL)
lwp->lwp_nostop++;
if (!cv_wait_sig(&l->cv, &l->lock)) {
if (lwp != NULL)
lwp->lwp_nostop--;
l->waiters--;
cv_broadcast(&l->cv);
mutex_exit(&l->lock);
return (EINTR);
}
if (lwp != NULL)
lwp->lwp_nostop--;
} else
cv_wait(&l->cv, &l->lock);
l->waiters--;
}
l->owner = curthread;
l->count--;
}
mutex_exit(&l->lock);
return (0);
}
/*
* If the lock is available, obtain it and return non-zero. If there is
* already a conflicting lock, return 0 immediately.
*/
int
smbfs_rw_tryenter(smbfs_rwlock_t *l, krw_t rw)
{
mutex_enter(&l->lock);
/*
* If this is a nested enter, then allow it. There
* must be as many exits as enters through.
*/
if (l->owner == curthread) {
/* lock is held for writing by current thread */
ASSERT(rw == RW_READER || rw == RW_WRITER);
l->count--;
} else if (rw == RW_READER) {
/*
* If there is a writer active or writers waiting, deny the
* lock. Otherwise, bump the count of readers.
*/
if (l->count < 0 || l->waiters > 0) {
mutex_exit(&l->lock);
return (0);
}
l->count++;
} else {
ASSERT(rw == RW_WRITER);
/*
* If there are readers active or a writer active, deny the
* lock. Otherwise, set the owner field to curthread and
* decrement count to indicate that a writer is active.
*/
if (l->count > 0 || l->owner != NULL) {
mutex_exit(&l->lock);
return (0);
}
l->owner = curthread;
l->count--;
}
mutex_exit(&l->lock);
return (1);
}
void
smbfs_rw_exit(smbfs_rwlock_t *l)
{
mutex_enter(&l->lock);
/*
* If this is releasing a writer lock, then increment count to
* indicate that there is one less writer active. If this was
* the last of possibly nested writer locks, then clear the owner
* field as well to indicate that there is no writer active
* and wakeup any possible waiting writers or readers.
*
* If releasing a reader lock, then just decrement count to
* indicate that there is one less reader active. If this was
* the last active reader and there are writer(s) waiting,
* then wake up the first.
*/
if (l->owner != NULL) {
ASSERT(l->owner == curthread);
l->count++;
if (l->count == 0) {
l->owner = NULL;
cv_broadcast(&l->cv);
}
} else {
ASSERT(l->count > 0);
l->count--;
if (l->count == 0 && l->waiters > 0)
cv_broadcast(&l->cv);
}
mutex_exit(&l->lock);
}
int
smbfs_rw_lock_held(smbfs_rwlock_t *l, krw_t rw)
{
if (rw == RW_READER)
return (l->count > 0);
ASSERT(rw == RW_WRITER);
return (l->count < 0);
}
/* ARGSUSED */
void
smbfs_rw_init(smbfs_rwlock_t *l, char *name, krw_type_t type, void *arg)
{
l->count = 0;
l->waiters = 0;
l->owner = NULL;
mutex_init(&l->lock, NULL, MUTEX_DEFAULT, NULL);
cv_init(&l->cv, NULL, CV_DEFAULT, NULL);
}
void
smbfs_rw_destroy(smbfs_rwlock_t *l)
{
mutex_destroy(&l->lock);
cv_destroy(&l->cv);
}