/*
 * 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.
 */


/*
 * This is the lock device driver.
 *
 * The lock driver provides a variation of inter-process mutexes with the
 * following twist in semantics:
 *	A waiter for a lock after a set timeout can "break" the lock and
 *	grab it from the current owner (without informing the owner).
 *
 * These semantics result in temporarily multiple processes thinking they
 * own the lock. This usually does not make sense for cases where locks are
 * used to protect a critical region and it is important to serialize access
 * to data structures. As breaking the lock will also lose the serialization
 * and result in corrupt data structures.
 *
 * The usage for winlock driver is primarily driven by the graphics system
 * when doing DGA (direct graphics access) graphics. The locks are used to
 * protect access to the frame buffer (presumably reflects back to the screen)
 * between competing processes that directly write to the screen as opposed
 * to going through the window server etc.
 * In this case, the result of breaking the lock at worst causes the screen
 * image to be distorted and is easily fixed by doing a "refresh"
 *
 * In well-behaved applications, the lock is held for a very short time and
 * the breaking semantics do not come into play. Not having this feature and
 * using normal inter-process mutexes will result in a misbehaved application
 * from grabbing the screen writing capability from the window manager and
 * effectively make the system look like it is hung (mouse pointer does not
 * move).
 *
 * A secondary aspect of the winlock driver is that it allows for extremely
 * fast lock acquire/release in cases where there is low contention. A memory
 * write is all that is needed (not even a function call). And the window
 * manager is the only DGA writer usually and this optimized for. Occasionally
 * some processes might do DGA graphics and cause kernel faults to handle
 * the contention/locking (and that has got to be slow!).
 *
 * The following IOCTLs are supported:
 *
 *   GRABPAGEALLOC:
 *	Compatibility with old cgsix device driver lockpage ioctls.
 *	Lockpages created this way must be an entire page for compatibility with
 *	older software.	 This ioctl allocates a lock context with its own
 *	private lock page.  The unique "ident" that identifies this lock is
 *	returned.
 *
 *   GRABPAGEFREE:
 *	Compatibility with cgsix device driver lockpage ioctls.	 This
 *	ioctl releases the lock context allocated by GRABPAGEALLOC.
 *
 *   GRABLOCKINFO:
 *	Returns a one-word flag.  '1' means that multiple clients may
 *	access this lock page.	Older device drivers returned '0',
 *	meaning that only two clients could access a lock page.
 *
 *   GRABATTACH:
 *	Not supported.	This ioctl would have grabbed all lock pages
 *	on behalf of the calling program.
 *
 *   WINLOCKALLOC:
 *	Allocate a lock context.  This ioctl accepts a key value.  as
 *	its argument.  If the key is zero, a new lock context is
 *	created, and its "ident" is returned.	If the key is nonzero,
 *	all existing contexts are checked to see if they match they
 *	key.  If a match is found, its reference count is incremented
 *	and its ident is returned, otherwise a new context is created
 *	and its ident is returned.
 *
 *   WINLOCKFREE:
 *	Free a lock context.  This ioctl accepts the ident of a lock
 *	context and decrements its reference count.  Once the reference
 *	count reaches zero *and* all mappings are released, the lock
 *	context is freed.  When all the lock context in the lock page are
 *	freed, the lock page is freed as well.
 *
 *   WINLOCKSETTIMEOUT:
 *	Set lock timeout for a context.	 This ioctl accepts the ident
 *	of a lock context and a timeout value in milliseconds.
 *	Whenever lock contention occurs, the timer is started and the lock is
 *	broken after the timeout expires. If timeout value is zero, lock does
 *	not timeout.  This value will be rounded to the nearest clock
 *	tick, so don't try to use it for real-time control or something.
 *
 *   WINLOCKGETTIMEOUT:
 *	Get lock timeout from a context.
 *
 *   WINLOCKDUMP:
 *	Dump state of this device.
 *
 *
 * How /dev/winlock works:
 *
 *   Every lock context consists of two mappings for the client to the lock
 *   page.  These mappings are known as the "lock page" and "unlock page"
 *   to the client. The first mmap to the lock context (identified by the
 *   sy_ident field returns during alloc) allocates mapping to the lock page,
 *   the second mmap allocates a mapping to the unlock page.
 *	The mappings dont have to be ordered in virtual address space, but do
 *   need to be ordered in time. Mapping and unmapping of these lock and unlock
 *   pages should happen in pairs. Doing them one at a time or unmapping one
 *   and leaving one mapped etc cause undefined behaviors.
 *	The mappings are always of length PAGESIZE, and type MAP_SHARED.
 *
 *   The first ioctl is to ALLOC a lock, either based on a key (if trying to
 *	grab a preexisting lock) or 0 (gets a default new one)
 *	This ioctl returns a value in sy_ident which is needed to do the
 *	later mmaps and FREE/other ioctls.
 *
 *   The "page number" portion of the sy_ident needs to be passed as the
 *	file offset when doing an mmap for both the lock page and unlock page
 *
 *   The value returned by mmap ( a user virtual address) needs to be
 *	incremented by the "page offset" portion of sy_ident to obtain the
 *	pointer to the actual lock. (Skipping this step, does not cause any
 *	visible error, but the process will be using the wrong lock!)
 *
 *	On a fork(), the child process will inherit the mappings for free, but
 *   will not inherit the parent's lock ownership if any. The child should NOT
 *   do an explicit FREE on the lock context unless it did an explicit ALLOC.
 *	Only one process at a time is allowed to have a valid hat
 *   mapping to a lock page. This is enforced by this driver.
 *   A client acquires a lock by writing a '1' to the lock page.
 *   Note, that it is not necessary to read and veryify that the lock is '0'
 *	prior to writing a '1' in it.
 *   If it does not already have a valid mapping to that page, the driver
 *   takes a fault (devmap_access), loads the client mapping
 *   and allows the client to continue.	 The client releases the lock by
 *   writing a '0' to the unlock page.	Again, if it does not have a valid
 *   mapping to the unlock page, the segment driver takes a fault,
 *   loads the mapping, and lets the client continue.  From this point
 *   forward, the client can make as many locks and unlocks as it
 *   wants, without any more faults into the kernel.
 *
 *   If a different process wants to acquire a lock, it takes a page fault
 *   when it writes the '1' to the lock page.  If the segment driver sees
 *   that the lock page contained a zero, then it invalidates the owner's
 *   mappings and gives the mappings to this process.
 *
 *   If there is already a '1' in the lock page when the second client
 *   tries to access the lock page, then a lock exists.	 The segment
 *   driver sleeps the second client and, if applicable, starts the
 *   timeout on the lock.  The owner's mapping to the unlock page
 *   is invalidated so that the driver will be woken again when the owner
 *   releases the lock.
 *
 *   When the locking client finally writes a '0' to the unlock page, the
 *   segment driver takes another fault.  The client is given a valid
 *   mapping, not to the unlock page, but to the "trash page", and allowed
 *   to continue.  Meanwhile, the sleeping client is given a valid mapping
 *   to the lock/unlock pages and allowed to continue as well.
 *
 * RFE: There is a leak if process exits before freeing allocated locks
 * But currently not tracking which locks were allocated by which
 * process and we do not have a clean entry point into the driver
 * to do garbage collection. If the interface used a file descriptor for each
 * lock it allocs, then the driver can free up stuff in the _close routine
 */

#include <sys/types.h>		/* various type defn's */
#include <sys/debug.h>
#include <sys/param.h>		/* various kernel limits */
#include <sys/time.h>
#include <sys/errno.h>
#include <sys/kmem.h>		/* defines kmem_alloc() */
#include <sys/conf.h>		/* defines cdevsw */
#include <sys/file.h>		/* various file modes, etc. */
#include <sys/uio.h>		/* UIO stuff */
#include <sys/ioctl.h>
#include <sys/cred.h>		/* defines cred struct */
#include <sys/mman.h>		/* defines mmap(2) parameters */
#include <sys/stat.h>		/* defines S_IFCHR */
#include <sys/cmn_err.h>	/* use cmn_err */
#include <sys/ddi.h>		/* ddi stuff */
#include <sys/sunddi.h>		/* ddi stuff */
#include <sys/ddi_impldefs.h>	/* ddi stuff */
#include <sys/winlockio.h>	/* defines ioctls, flags, data structs */

static int	winlock_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int	winlock_devmap(dev_t, devmap_cookie_t, offset_t, size_t,
			size_t *, uint_t);
static int	winlocksegmap(dev_t, off_t, struct as *, caddr_t *, off_t,
			uint_t, uint_t, uint_t, cred_t *);

static struct cb_ops	winlock_cb_ops = {
	nulldev,		/* open */
	nulldev,		/* close */
	nodev,			/* strategy */
	nodev,			/* print */
	nodev,			/* dump */
	nodev,			/* read */
	nodev,			/* write */
	winlock_ioctl,		/* ioctl */
	winlock_devmap,		/* devmap */
	nodev,			/* mmap */
	winlocksegmap,		/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* prop_op */
	NULL,			/* streamtab */
	D_NEW|D_MP|D_DEVMAP,	/* Driver compatibility flag */
	0,			/* rev */
	nodev,			/* aread */
	nodev			/* awrite */
};

static int winlock_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int winlock_attach(dev_info_t *, ddi_attach_cmd_t);
static int winlock_detach(dev_info_t *, ddi_detach_cmd_t);

static struct dev_ops	winlock_ops = {
	DEVO_REV,
	0,			/* refcount */
	winlock_info,		/* info */
	nulldev,		/* identify */
	nulldev,		/* probe */
	winlock_attach,		/* attach */
	winlock_detach,		/* detach */
	nodev,			/* reset */
	&winlock_cb_ops,	/* driver ops */
	NULL,			/* bus ops */
	NULL,			/* power */
	ddi_quiesce_not_needed,		/* quiesce */
};

static int winlockmap_map(devmap_cookie_t, dev_t, uint_t, offset_t, size_t,
		void **);
static void winlockmap_unmap(devmap_cookie_t, void *, offset_t, size_t,
		devmap_cookie_t, void **, devmap_cookie_t, void **);
static int winlockmap_dup(devmap_cookie_t, void *,
		devmap_cookie_t, void **);
static int winlockmap_access(devmap_cookie_t, void *, offset_t, size_t,
		uint_t, uint_t);

static
struct devmap_callback_ctl winlockmap_ops = {
	DEVMAP_OPS_REV,
	winlockmap_map,
	winlockmap_access,
	winlockmap_dup,
	winlockmap_unmap,
};

#if DEBUG
static	int	lock_debug = 0;
#define	DEBUGF(level, args)	{ if (lock_debug >= (level)) cmn_err args; }
#else
#define	DEBUGF(level, args)
#endif

/* Driver supports two styles of locks */
enum winlock_style { NEWSTYLE_LOCK, OLDSTYLE_LOCK };

/*
 * These structures describe a lock context.  We permit multiple
 * clients (not just two) to access a lock page
 *
 * The "cookie" identifies the lock context. It is the page number portion
 * sy_ident returned on lock allocation. Cookie is used in later ioctls.
 * "cookie" is lockid * PAGESIZE
 * "lockptr" is the kernel virtual address to the lock itself
 * The page offset portion of lockptr is the page offset portion of sy_ident
 */

/*
 * per-process information about locks.  This is the private field of
 * a devmap mapping.  Note that usually *two* mappings point to this.
 */

/*
 * Each process using winlock is associated with a segproc structure
 * In various driver entry points, we need to search to find the right
 * segproc structure (If we were using file handles for each lock this
 * would not have been necessary).
 * It would have been simple to use the process pid (and ddi_get_pid)
 * However, during fork devmap_dup is called in the parent process context
 * and using the pid complicates the code by introducing orphans.
 * Instead we use the as pointer for the process as a cookie
 * which requires delving into various non-DDI kosher structs
 */
typedef struct segproc {
	struct segproc	*next;		/* next client of this lock */
	struct seglock	*lp;		/* associated lock context */
	devmap_cookie_t	lockseg;	/* lock mapping, if any */
	devmap_cookie_t unlockseg;	/* unlock mapping, if any */
	void		*tag;		/* process as pointer as tag */
	uint_t		flag;		/* see "flag bits" in winlockio.h */
} SegProc;

#define	ID(sdp)		((sdp)->tag)
#define	CURPROC_ID	(void *)(curproc->p_as)

/* per lock context information */

typedef struct seglock {
	struct seglock	*next;		/* next lock */
	uint_t		sleepers;	/* nthreads sleeping on this lock */
	uint_t		alloccount;	/* how many times created? */
	uint_t		cookie;		/* mmap() offset (page #) into device */
	uint_t		key;		/* key, if any */
	enum winlock_style	style;	/* style of lock - OLDSTYLE, NEWSTYLE */
	clock_t		timeout;	/* sleep time in ticks */
	ddi_umem_cookie_t umem_cookie;	/* cookie for umem allocated memory */
	int		*lockptr;	/* kernel virtual addr of lock */
	struct segproc	*clients;	/* list of clients of this lock */
	struct segproc	*owner;		/* current owner of lock */
	kmutex_t	mutex;		/* mutex for lock */
	kcondvar_t	locksleep;	/* for sleeping on lock */
} SegLock;

#define	LOCK(lp)	(*((lp)->lockptr))

/*
 * Number of locks that can fit in a page. Driver can support only that many.
 * For oldsytle locks, it is relatively easy to increase the limit as each
 * is in a separate page (MAX_LOCKS mostly serves to prevent runaway allocation
 * For newstyle locks, this is trickier as the code needs to allow for mapping
 * into the second or third page of the cookie for some locks.
 */
#define	MAX_LOCKS	(PAGESIZE/sizeof (int))

#define	LOCKTIME	3	/* Default lock timeout in seconds */


/* Protections setting for winlock user mappings */
#define	WINLOCK_PROT	(PROT_READ|PROT_WRITE|PROT_USER)

/*
 * The trash page is where unwanted writes go
 * when a process is releasing a lock.
 */
static	ddi_umem_cookie_t trashpage_cookie = NULL;

/* For newstyle allocations a common page of locks is used */
static	caddr_t	lockpage = NULL;
static	ddi_umem_cookie_t lockpage_cookie = NULL;

static	dev_info_t	*winlock_dip = NULL;
static	kmutex_t	winlock_mutex;

/*
 * winlock_mutex protects
 *	lock_list
 *	lock_free_list
 *	"next" field in SegLock
 *	next_lock
 *	trashpage_cookie
 *	lockpage & lockpage_cookie
 *
 * SegLock_mutex protects
 *	rest of fields in SegLock
 *	All fields in list of SegProc (lp->clients)
 *
 * Lock ordering is winlock_mutex->SegLock_mutex
 * During devmap/seg operations SegLock_mutex acquired without winlock_mutex
 *
 * During devmap callbacks, the pointer to SegProc is stored as the private
 * data in the devmap handle. This pointer will not go stale (i.e., the
 * SegProc getting deleted) as the SegProc is not deleted until both the
 * lockseg and unlockseg have been unmapped and the pointers stored in
 * the devmap handles have been NULL'ed.
 * But before this pointer is used to access any fields (other than the 'lp')
 * lp->mutex must be held.
 */

/*
 * The allocation code tries to allocate from lock_free_list
 * first, otherwise it uses kmem_zalloc.  When lock list is idle, all
 * locks in lock_free_list are kmem_freed
 */
static	SegLock	*lock_list = NULL;		/* in-use locks */
static	SegLock	*lock_free_list = NULL;		/* free locks */
static	int	next_lock = 0;			/* next lock cookie */

/* Routines to find a lock in lock_list based on offset or key */
static SegLock *seglock_findlock(uint_t);
static SegLock *seglock_findkey(uint_t);

/* Routines to find and allocate SegProc structures */
static SegProc *seglock_find_specific(SegLock *, void *);
static SegProc *seglock_alloc_specific(SegLock *, void *);
#define	seglock_findclient(lp)	seglock_find_specific((lp), CURPROC_ID)
#define	seglock_allocclient(lp)	seglock_alloc_specific((lp), CURPROC_ID)

/* Delete client from lock's client list */
static void seglock_deleteclient(SegLock *, SegProc *);
static void garbage_collect_lock(SegLock *, SegProc *);

/* Create a new lock */
static SegLock *seglock_createlock(enum winlock_style);
/* Destroy lock */
static void seglock_destroylock(SegLock *);
static void lock_destroyall(void);

/* Helper functions in winlockmap_access */
static int give_mapping(SegLock *, SegProc *, uint_t);
static int lock_giveup(SegLock *, int);
static int seglock_lockfault(devmap_cookie_t, SegProc *, SegLock *, uint_t);

/* routines called from ioctl */
static int seglock_graballoc(intptr_t, enum winlock_style, int);
static int seglock_grabinfo(intptr_t, int);
static int seglock_grabfree(intptr_t, int);
static int seglock_gettimeout(intptr_t, int);
static int seglock_settimeout(intptr_t, int);
static void seglock_dump_all(void);

static	int
winlock_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	DEBUGF(1, (CE_CONT, "winlock_attach, devi=%p, cmd=%d\n",
	    (void *)devi, (int)cmd));
	if (cmd != DDI_ATTACH)
		return (DDI_FAILURE);
	if (ddi_create_minor_node(devi, "winlock", S_IFCHR, 0, DDI_PSEUDO, 0)
	    == DDI_FAILURE) {
		return (DDI_FAILURE);
	}
	winlock_dip = devi;
	ddi_report_dev(devi);
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static	int
winlock_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	DEBUGF(1, (CE_CONT, "winlock_detach, devi=%p, cmd=%d\n",
	    (void *)devi, (int)cmd));
	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	mutex_enter(&winlock_mutex);
	if (lock_list != NULL) {
		mutex_exit(&winlock_mutex);
		return (DDI_FAILURE);
	}
	ASSERT(lock_free_list == NULL);

	DEBUGF(1, (CE_CONT, "detach freeing trashpage and lockpage\n"));
	/* destroy any common stuff created */
	if (trashpage_cookie != NULL) {
		ddi_umem_free(trashpage_cookie);
		trashpage_cookie = NULL;
	}
	if (lockpage != NULL) {
		ddi_umem_free(lockpage_cookie);
		lockpage = NULL;
		lockpage_cookie = NULL;
	}
	winlock_dip = NULL;
	mutex_exit(&winlock_mutex);
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static	int
winlock_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	register int error;

	/* initialize result */
	*result = NULL;

	/* only valid instance (i.e., getminor) is 0 */
	if (getminor((dev_t)arg) >= 1)
		return (DDI_FAILURE);

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (winlock_dip == NULL)
			error = DDI_FAILURE;
		else {
			*result = (void *)winlock_dip;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}


/*ARGSUSED*/
int
winlock_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
	cred_t *cred, int *rval)
{
	DEBUGF(1, (CE_CONT, "winlockioctl: cmd=%d, arg=0x%p\n",
	    cmd, (void *)arg));

	switch (cmd) {
	/*
	 * ioctls that used to be handled by framebuffers (defined in fbio.h)
	 * RFE: No code really calls the GRAB* ioctls now. Should EOL.
	 */

	case GRABPAGEALLOC:
		return (seglock_graballoc(arg, OLDSTYLE_LOCK, mode));
	case GRABPAGEFREE:
		return (seglock_grabfree(arg, mode));
	case GRABLOCKINFO:
		return (seglock_grabinfo(arg, mode));
	case GRABATTACH:
		return (EINVAL); /* GRABATTACH is not supported (never was) */

	case WINLOCKALLOC:
		return (seglock_graballoc(arg, NEWSTYLE_LOCK, mode));
	case WINLOCKFREE:
		return (seglock_grabfree(arg, mode));
	case WINLOCKSETTIMEOUT:
		return (seglock_settimeout(arg, mode));
	case WINLOCKGETTIMEOUT:
		return (seglock_gettimeout(arg, mode));
	case WINLOCKDUMP:
		seglock_dump_all();
		return (0);

#ifdef DEBUG
	case (WIOC|255):
		lock_debug = arg;
		return (0);
#endif

	default:
		return (ENOTTY);		/* Why is this not EINVAL */
	}
}

int
winlocksegmap(
	dev_t	dev,		/* major:minor */
	off_t	off,		/* device offset from mmap(2) */
	struct as *as,		/* user's address space. */
	caddr_t	*addr,		/* address from mmap(2) */
	off_t	len,		/* length from mmap(2) */
	uint_t	prot,		/* user wants this access */
	uint_t	maxprot,	/* this is the maximum the user can have */
	uint_t	flags,		/* flags from mmap(2) */
	cred_t	*cred)
{
	DEBUGF(1, (CE_CONT, "winlock_segmap off=%lx, len=0x%lx\n", off, len));

	/* Only MAP_SHARED mappings are supported */
	if ((flags & MAP_TYPE) == MAP_PRIVATE) {
		return (EINVAL);
	}

	/* Use devmap_setup to setup the mapping */
	return (devmap_setup(dev, (offset_t)off, as, addr, (size_t)len, prot,
	    maxprot, flags, cred));
}

/*ARGSUSED*/
int
winlock_devmap(dev_t dev, devmap_cookie_t dhp, offset_t off, size_t len,
    size_t *maplen, uint_t model)
{
	SegLock *lp;
	int err;

	DEBUGF(1, (CE_CONT, "winlock devmap: off=%llx, len=%lx, dhp=%p\n",
	    off, len, (void *)dhp));

	*maplen = 0;

	/* Check if the lock exists, i.e., has been created by alloc */
	/* off is the sy_ident returned in the alloc ioctl */
	if ((lp = seglock_findlock((uint_t)off)) == NULL) {
		return (ENXIO);
	}

	/*
	 * The offset bits in mmap(2) offset has to be same as in lockptr
	 * OR the offset should be 0 (i.e. masked off)
	 */
	if (((off & PAGEOFFSET) != 0) &&
	    ((off ^ (uintptr_t)(lp->lockptr)) & (offset_t)PAGEOFFSET) != 0) {
		DEBUGF(2, (CE_CONT,
		    "mmap offset %llx mismatch with lockptr %p\n",
		    off, (void *)lp->lockptr));
		mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
		return (EINVAL);
	}

	/* Only supports PAGESIZE length mappings */
	if (len != PAGESIZE) {
		mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
		return (EINVAL);
	}

	/*
	 * Set up devmap to point at page associated with lock
	 * RFE: At this point we dont know if this is a lockpage or unlockpage
	 * a lockpage would not need DEVMAP_ALLOW_REMAP setting
	 * We could have kept track of the mapping order here,
	 * but devmap framework does not support storing any state in this
	 * devmap callback as it does not callback for error cleanup if some
	 * other error happens in the framework.
	 * RFE: We should modify the winlock mmap interface so that the
	 * user process marks in the offset passed in whether this is for a
	 * lock or unlock mapping instead of guessing based on order of maps
	 * This would cleanup other things (such as in fork)
	 */
	if ((err = devmap_umem_setup(dhp, winlock_dip, &winlockmap_ops,
	    lp->umem_cookie, 0, PAGESIZE, WINLOCK_PROT,
	    DEVMAP_ALLOW_REMAP, 0)) < 0) {
		mutex_exit(&lp->mutex);	/* held by seglock_findlock */
		return (err);
	}
	/*
	 * No mappings are loaded to those segments yet. The correctness
	 * of the winlock semantics depends on the devmap framework/seg_dev NOT
	 * loading the translations without calling _access callback.
	 */

	mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
	*maplen = PAGESIZE;
	return (0);
}

/*
 * This routine is called by the devmap framework after the devmap entry point
 * above and the mapping is setup in seg_dev.
 * We store the pointer to the per-process context in the devmap private data.
 */
/*ARGSUSED*/
static int
winlockmap_map(devmap_cookie_t dhp, dev_t dev, uint_t flags, offset_t off,
	size_t len, void **pvtp)
{
	SegLock *lp = seglock_findlock((uint_t)off); /* returns w/ mutex held */
	SegProc *sdp;

	ASSERT(len == PAGESIZE);

	/* Find the per-process context for this lock, alloc one if not found */
	sdp = seglock_allocclient(lp);

	/*
	 * RFE: Determining which is a lock vs unlock seg is based on order
	 * of mmaps, we should change that to be derivable from off
	 */
	if (sdp->lockseg == NULL) {
		sdp->lockseg = dhp;
	} else if (sdp->unlockseg == NULL) {
		sdp->unlockseg = dhp;
	} else {
		/* attempting to map lock more than twice */
		mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
		return (ENOMEM);
	}

	*pvtp = sdp;
	mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
	return (DDI_SUCCESS);
}

/*
 * duplicate a segment, as in fork()
 * On fork, the child inherits the mappings to the lock
 *	lp->alloccount is NOT incremented, so child should not do a free().
 *	Semantics same as if done an alloc(), map(), map().
 *	This way it would work fine if doing an exec() variant later
 *	Child does not inherit any UFLAGS set in parent
 * The lock and unlock pages are started off unmapped, i.e., child does not
 *	own the lock.
 * The code assumes that the child process has a valid pid at this point
 * RFE: This semantics depends on fork not duplicating the hat mappings
 *	(which is the current implementation). To enforce it would need to
 *	call devmap_unload from here - not clear if that is allowed.
 */

static int
winlockmap_dup(devmap_cookie_t dhp, void *oldpvt, devmap_cookie_t new_dhp,
	void **newpvt)
{
	SegProc *sdp = (SegProc *)oldpvt;
	SegProc *ndp;
	SegLock *lp = sdp->lp;

	mutex_enter(&lp->mutex);
	ASSERT((dhp == sdp->lockseg) || (dhp == sdp->unlockseg));

	/*
	 * Note: At this point, the child process does have a pid, but
	 * the arguments passed to as_dup and hence to devmap_dup dont pass it
	 * down. So we cannot use normal seglock_findclient - which finds the
	 * parent sdp itself!
	 * Instead we allocate the child's SegProc by using the child as pointer
	 * RFE: we are using the as stucture which means peeking into the
	 * devmap_cookie. This is not DDI-compliant. Need a compliant way of
	 * getting at either the as or, better, a way to get the child's new pid
	 */
	ndp = seglock_alloc_specific(lp,
	    (void *)((devmap_handle_t *)new_dhp)->dh_seg->s_as);
	ASSERT(ndp != sdp);

	if (sdp->lockseg == dhp) {
		ASSERT(ndp->lockseg == NULL);
		ndp->lockseg = new_dhp;
	} else {
		ASSERT(sdp->unlockseg == dhp);
		ASSERT(ndp->unlockseg == NULL);
		ndp->unlockseg = new_dhp;
		if (sdp->flag & TRASHPAGE) {
			ndp->flag |= TRASHPAGE;
		}
	}
	mutex_exit(&lp->mutex);
	*newpvt = (void *)ndp;
	return (0);
}


/*ARGSUSED*/
static void
winlockmap_unmap(devmap_cookie_t dhp, void *pvtp, offset_t off, size_t len,
	devmap_cookie_t new_dhp1, void **newpvtp1,
	devmap_cookie_t new_dhp2, void **newpvtp2)
{
	SegProc	*sdp = (SegProc *)pvtp;
	SegLock	*lp = sdp->lp;

	/*
	 * We always create PAGESIZE length mappings, so there should never
	 * be a partial unmapping case
	 */
	ASSERT((new_dhp1 == NULL) && (new_dhp2 == NULL));

	mutex_enter(&lp->mutex);
	ASSERT((dhp == sdp->lockseg) || (dhp == sdp->unlockseg));
	/* make sure this process doesn't own the lock */
	if (sdp == lp->owner) {
		/*
		 * Not handling errors - i.e., errors in unloading mapping
		 * As part of unmapping hat/seg structure get torn down anyway
		 */
		(void) lock_giveup(lp, 0);
	}

	ASSERT(sdp != lp->owner);
	if (sdp->lockseg == dhp) {
		sdp->lockseg = NULL;
	} else {
		ASSERT(sdp->unlockseg == dhp);
		sdp->unlockseg = NULL;
		sdp->flag &= ~TRASHPAGE;	/* clear flag if set */
	}

	garbage_collect_lock(lp, sdp);
}

/*ARGSUSED*/
static int
winlockmap_access(devmap_cookie_t dhp, void *pvt, offset_t off, size_t len,
	uint_t type, uint_t rw)
{
	SegProc *sdp = (SegProc *)pvt;
	SegLock *lp = sdp->lp;
	int err;

	/* Driver handles only DEVMAP_ACCESS type of faults */
	if (type != DEVMAP_ACCESS)
		return (-1);

	mutex_enter(&lp->mutex);
	ASSERT((dhp == sdp->lockseg) || (dhp == sdp->unlockseg));

	/* should be using a SegProc that corresponds to current process */
	ASSERT(ID(sdp) == CURPROC_ID);

	/*
	 * If process is faulting but does not have both segments mapped
	 * return error (should cause a segv).
	 * RFE: could give it a permanent trashpage
	 */
	if ((sdp->lockseg == NULL) || (sdp->unlockseg == NULL)) {
		err = -1;
	} else {
		err = seglock_lockfault(dhp, sdp, lp, rw);
	}
	mutex_exit(&lp->mutex);
	return (err);
}

	/* INTERNAL ROUTINES START HERE */



/*
 * search the lock_list list for the specified cookie
 * The cookie is the sy_ident field returns by ALLOC ioctl.
 * This has two parts:
 * the pageoffset bits contain offset into the lock page.
 * the pagenumber bits contain the lock id.
 * The user code is supposed to pass in only the pagenumber portion
 *	(i.e. mask off the pageoffset bits). However the code below
 *	does the mask in case the users are not diligent
 * if found, returns with mutex for SegLock structure held
 */
static SegLock *
seglock_findlock(uint_t cookie)
{
	SegLock	*lp;

	cookie &= (uint_t)PAGEMASK;   /* remove pageoffset bits to get cookie */
	mutex_enter(&winlock_mutex);
	for (lp = lock_list; lp != NULL; lp = lp->next) {
		mutex_enter(&lp->mutex);
		if (cookie == lp->cookie) {
			break;	/* return with lp->mutex held */
		}
		mutex_exit(&lp->mutex);
	}
	mutex_exit(&winlock_mutex);
	return (lp);
}

/*
 * search the lock_list list for the specified non-zero key
 * if found, returns with lock for SegLock structure held
 */
static SegLock *
seglock_findkey(uint_t key)
{
	SegLock	*lp;

	ASSERT(MUTEX_HELD(&winlock_mutex));
	/* The driver allows multiple locks with key 0, dont search */
	if (key == 0)
		return (NULL);
	for (lp = lock_list; lp != NULL; lp = lp->next) {
		mutex_enter(&lp->mutex);
		if (key == lp->key)
			break;
		mutex_exit(&lp->mutex);
	}
	return (lp);
}

/*
 * Create a new lock context.
 * Returns with SegLock mutex held
 */

static SegLock *
seglock_createlock(enum winlock_style style)
{
	SegLock	*lp;

	DEBUGF(3, (CE_CONT, "seglock_createlock: free_list=%p, next_lock %d\n",
	    (void *)lock_free_list, next_lock));

	ASSERT(MUTEX_HELD(&winlock_mutex));
	if (lock_free_list != NULL) {
		lp = lock_free_list;
		lock_free_list = lp->next;
	} else if (next_lock >= MAX_LOCKS) {
		return (NULL);
	} else {
		lp = kmem_zalloc(sizeof (SegLock), KM_SLEEP);
		lp->cookie = (next_lock + 1) * (uint_t)PAGESIZE;
		mutex_init(&lp->mutex, NULL, MUTEX_DEFAULT, NULL);
		cv_init(&lp->locksleep, NULL, CV_DEFAULT, NULL);
		++next_lock;
	}

	mutex_enter(&lp->mutex);
	ASSERT((lp->cookie/PAGESIZE) <= next_lock);

	if (style == OLDSTYLE_LOCK) {
		lp->lockptr = (int *)ddi_umem_alloc(PAGESIZE,
		    DDI_UMEM_SLEEP, &(lp->umem_cookie));
	} else {
		lp->lockptr = ((int *)lockpage) + ((lp->cookie/PAGESIZE) - 1);
		lp->umem_cookie = lockpage_cookie;
	}

	ASSERT(lp->lockptr != NULL);
	lp->style = style;
	lp->sleepers = 0;
	lp->alloccount = 1;
	lp->timeout = LOCKTIME*hz;
	lp->clients = NULL;
	lp->owner = NULL;
	LOCK(lp) = 0;
	lp->next = lock_list;
	lock_list = lp;
	return (lp);
}

/*
 * Routine to destory a lock structure.
 * This routine is called while holding the lp->mutex but not the
 * winlock_mutex.
 */

static void
seglock_destroylock(SegLock *lp)
{
	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(!MUTEX_HELD(&winlock_mutex));

	DEBUGF(3, (CE_CONT, "destroying lock cookie %d key %d\n",
	    lp->cookie, lp->key));

	ASSERT(lp->alloccount == 0);
	ASSERT(lp->clients == NULL);
	ASSERT(lp->owner == NULL);
	ASSERT(lp->sleepers == 0);

	/* clean up/release fields in lp */
	if (lp->style == OLDSTYLE_LOCK) {
		ddi_umem_free(lp->umem_cookie);
	}
	lp->umem_cookie = NULL;
	lp->lockptr = NULL;
	lp->key = 0;

	/*
	 * Reduce cookie by 1, makes it non page-aligned and invalid
	 * This prevents any valid lookup from finding this lock
	 * so when we drop the lock and regrab it it will still
	 * be there and nobody else would have attached to it
	 */
	lp->cookie--;

	/* Drop and reacquire mutexes in right order */
	mutex_exit(&lp->mutex);
	mutex_enter(&winlock_mutex);
	mutex_enter(&lp->mutex);

	/* reincrement the cookie to get the original valid cookie */
	lp->cookie++;
	ASSERT((lp->cookie & PAGEOFFSET) == 0);
	ASSERT(lp->alloccount == 0);
	ASSERT(lp->clients == NULL);
	ASSERT(lp->owner == NULL);
	ASSERT(lp->sleepers == 0);

	/* Remove lp from lock_list */
	if (lock_list == lp) {
		lock_list = lp->next;
	} else {
		SegLock *tmp = lock_list;
		while (tmp->next != lp) {
			tmp = tmp->next;
			ASSERT(tmp != NULL);
		}
		tmp->next = lp->next;
	}

	/* Add to lock_free_list */
	lp->next = lock_free_list;
	lock_free_list = lp;
	mutex_exit(&lp->mutex);

	/* Check if all locks deleted and cleanup */
	if (lock_list == NULL) {
		lock_destroyall();
	}

	mutex_exit(&winlock_mutex);
}

/* Routine to find a SegProc corresponding to the tag */

static SegProc *
seglock_find_specific(SegLock *lp, void *tag)
{
	SegProc *sdp;

	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(tag != NULL);
	for (sdp = lp->clients; sdp != NULL; sdp = sdp->next) {
		if (ID(sdp) == tag)
			break;
	}
	return (sdp);
}

/* Routine to find (and if needed allocate) a SegProc corresponding to tag */

static SegProc *
seglock_alloc_specific(SegLock *lp, void *tag)
{
	SegProc *sdp;

	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(tag != NULL);

	/* Search and return if existing one found */
	sdp = seglock_find_specific(lp, tag);
	if (sdp != NULL)
		return (sdp);

	DEBUGF(3, (CE_CONT, "Allocating segproc structure for tag %p lock %d\n",
	    tag, lp->cookie));

	/* Allocate a new SegProc */
	sdp = kmem_zalloc(sizeof (SegProc), KM_SLEEP);
	sdp->next = lp->clients;
	lp->clients = sdp;
	sdp->lp = lp;
	ID(sdp) = tag;
	return (sdp);
}

/*
 * search a context's client list for the given client and delete
 */

static void
seglock_deleteclient(SegLock *lp, SegProc *sdp)
{
	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(lp->owner != sdp);	/* Not current owner of lock */
	ASSERT(sdp->lockseg == NULL);	/* Mappings torn down */
	ASSERT(sdp->unlockseg == NULL);

	DEBUGF(3, (CE_CONT, "Deleting segproc structure for pid %d lock %d\n",
	    ddi_get_pid(), lp->cookie));
	if (lp->clients == sdp) {
		lp->clients = sdp->next;
	} else {
		SegProc *tmp = lp->clients;
		while (tmp->next != sdp) {
			tmp = tmp->next;
			ASSERT(tmp != NULL);
		}
		tmp->next = sdp->next;
	}
	kmem_free(sdp, sizeof (SegProc));
}

/*
 * Routine to verify if a SegProc and SegLock
 * structures are empty/idle.
 * Destroys the structures if they are ready
 * Can be called with sdp == NULL if want to verify only the lock state
 * caller should hold the lp->mutex
 * and this routine drops the mutex
 */
static void
garbage_collect_lock(SegLock *lp, SegProc *sdp)
{
	ASSERT(MUTEX_HELD(&lp->mutex));
	/* see if both segments unmapped from client structure */
	if ((sdp != NULL) && (sdp->lockseg == NULL) && (sdp->unlockseg == NULL))
		seglock_deleteclient(lp, sdp);

	/* see if this is last client in the entire lock context */
	if ((lp->clients == NULL) && (lp->alloccount == 0)) {
		seglock_destroylock(lp);
	} else {
		mutex_exit(&lp->mutex);
	}
}


/* IOCTLS START HERE */

static int
seglock_grabinfo(intptr_t arg, int mode)
{
	int i = 1;

	/* multiple clients per lock supported - see comments up top */
	if (ddi_copyout((caddr_t)&i, (caddr_t)arg, sizeof (int), mode) != 0)
		return (EFAULT);
	return (0);
}

static int
seglock_graballoc(intptr_t arg, enum winlock_style style, int mode) /* IOCTL */
{
	struct seglock	*lp;
	uint_t		key;
	struct		winlockalloc wla;
	int		err;

	if (style == OLDSTYLE_LOCK) {
		key = 0;
	} else {
		if (ddi_copyin((caddr_t)arg, (caddr_t)&wla, sizeof (wla),
		    mode)) {
			return (EFAULT);
		}
		key = wla.sy_key;
	}

	DEBUGF(3, (CE_CONT,
	    "seglock_graballoc: key=%u, style=%d\n", key, style));

	mutex_enter(&winlock_mutex);
	/* Allocate lockpage on first new style alloc */
	if ((lockpage == NULL) && (style == NEWSTYLE_LOCK)) {
		lockpage = ddi_umem_alloc(PAGESIZE, DDI_UMEM_SLEEP,
		    &lockpage_cookie);
	}

	/* Allocate trashpage on first alloc (any style) */
	if (trashpage_cookie == NULL) {
		(void) ddi_umem_alloc(PAGESIZE, DDI_UMEM_TRASH | DDI_UMEM_SLEEP,
		    &trashpage_cookie);
	}

	if ((lp = seglock_findkey(key)) != NULL) {
		DEBUGF(2, (CE_CONT, "alloc: found lock key %d cookie %d\n",
		    key, lp->cookie));
		++lp->alloccount;
	} else if ((lp = seglock_createlock(style)) != NULL) {
		DEBUGF(2, (CE_CONT, "alloc: created lock key %d cookie %d\n",
		    key, lp->cookie));
		lp->key = key;
	} else {
		DEBUGF(2, (CE_CONT, "alloc: cannot create lock key %d\n", key));
		mutex_exit(&winlock_mutex);
		return (ENOMEM);
	}
	ASSERT((lp != NULL) && MUTEX_HELD(&lp->mutex));

	mutex_exit(&winlock_mutex);

	if (style == OLDSTYLE_LOCK) {
		err = ddi_copyout((caddr_t)&lp->cookie, (caddr_t)arg,
		    sizeof (lp->cookie), mode);
	} else {
		wla.sy_ident = lp->cookie +
		    (uint_t)((uintptr_t)(lp->lockptr) & PAGEOFFSET);
		err = ddi_copyout((caddr_t)&wla, (caddr_t)arg,
		    sizeof (wla), mode);
	}

	if (err) {
		/* On error, should undo allocation */
		lp->alloccount--;

		/* Verify and delete if lock is unused now */
		garbage_collect_lock(lp, NULL);
		return (EFAULT);
	}

	mutex_exit(&lp->mutex);
	return (0);
}

static int
seglock_grabfree(intptr_t arg, int mode)	/* IOCTL */
{
	struct seglock	*lp;
	uint_t	offset;

	if (ddi_copyin((caddr_t)arg, &offset, sizeof (offset), mode)
	    != 0) {
		return (EFAULT);
	}
	DEBUGF(2, (CE_CONT, "seglock_grabfree: offset=%u", offset));

	if ((lp = seglock_findlock(offset)) == NULL) {
		DEBUGF(2, (CE_CONT, "did not find lock\n"));
		return (EINVAL);
	}
	DEBUGF(3, (CE_CONT, " lock key %d, cookie %d, alloccount %d\n",
	    lp->key, lp->cookie, lp->alloccount));

	if (lp->alloccount > 0)
		lp->alloccount--;

	/* Verify and delete if lock is unused now */
	garbage_collect_lock(lp, NULL);
	return (0);
}


/*
 * Sets timeout in lock and UFLAGS in client
 *	the UFLAGS are stored in the client structure and persistent only
 *	till the unmap of the lock pages. If the process sets UFLAGS
 *	does a map of the lock/unlock pages and unmaps them, the client
 *	structure will get deleted and the UFLAGS will be lost. The process
 *	will need to resetup the flags.
 */
static int
seglock_settimeout(intptr_t arg, int mode)	/* IOCTL */
{
	SegLock		*lp;
	SegProc		*sdp;
	struct winlocktimeout		wlt;

	if (ddi_copyin((caddr_t)arg, &wlt, sizeof (wlt), mode) != 0) {
		return (EFAULT);
	}

	if ((lp = seglock_findlock(wlt.sy_ident)) == NULL)
		return (EINVAL);

	lp->timeout = MSEC_TO_TICK_ROUNDUP(wlt.sy_timeout);
	/* if timeout modified, wake up any sleepers */
	if (lp->sleepers > 0) {
		cv_broadcast(&lp->locksleep);
	}

	/*
	 * If the process is trying to set UFLAGS,
	 *	Find the client segproc and allocate one if needed
	 *	Set the flags preserving the kernel flags
	 * If the process is clearing UFLAGS
	 *	Find the client segproc but dont allocate one if does not exist
	 */
	if (wlt.sy_flags & UFLAGS) {
		sdp = seglock_allocclient(lp);
		sdp->flag = sdp->flag & KFLAGS | wlt.sy_flags & UFLAGS;
	} else if ((sdp = seglock_findclient(lp)) != NULL) {
		sdp->flag = sdp->flag & KFLAGS;
		/* If clearing UFLAGS leaves the segment or lock idle, delete */
		garbage_collect_lock(lp, sdp);
		return (0);
	}
	mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */
	return (0);
}

static int
seglock_gettimeout(intptr_t arg, int mode)
{
	SegLock		*lp;
	SegProc		*sdp;
	struct winlocktimeout		wlt;

	if (ddi_copyin((caddr_t)arg, &wlt, sizeof (wlt), mode) != 0)
		return (EFAULT);

	if ((lp = seglock_findlock(wlt.sy_ident)) == NULL)
		return (EINVAL);

	wlt.sy_timeout = TICK_TO_MSEC(lp->timeout);
	/*
	 * If this process has an active allocated lock return those flags
	 *	Dont allocate a client structure on gettimeout
	 * If not, return 0.
	 */
	if ((sdp = seglock_findclient(lp)) != NULL) {
		wlt.sy_flags = sdp->flag & UFLAGS;
	} else {
		wlt.sy_flags = 0;
	}
	mutex_exit(&lp->mutex);	/* mutex held by seglock_findlock */

	if (ddi_copyout(&wlt, (caddr_t)arg, sizeof (wlt), mode) != 0)
		return (EFAULT);

	return (0);
}

/*
 * Handle lock segment faults here...
 *
 * This is where the magic happens.
 */

/* ARGSUSED */
static	int
seglock_lockfault(devmap_cookie_t dhp, SegProc *sdp, SegLock *lp, uint_t rw)
{
	SegProc *owner = lp->owner;
	int err;

	ASSERT(MUTEX_HELD(&lp->mutex));
	DEBUGF(3, (CE_CONT,
	    "seglock_lockfault: hdl=%p, sdp=%p, lp=%p owner=%p\n",
	    (void *)dhp, (void *)sdp, (void *)lp, (void *)owner));

	/* lockfault is always called with sdp in current process context */
	ASSERT(ID(sdp) == CURPROC_ID);

	/* If Lock has no current owner, give the mapping to new owner */
	if (owner == NULL) {
		DEBUGF(4, (CE_CONT, " lock has no current owner\n"));
		return (give_mapping(lp, sdp, rw));
	}

	if (owner == sdp) {
		/*
		 * Current owner is faulting on owned lock segment OR
		 * Current owner is faulting on unlock page and has no waiters
		 * Then can give the mapping to current owner
		 */
		if ((sdp->lockseg == dhp) || (lp->sleepers == 0)) {
		DEBUGF(4, (CE_CONT, "lock owner faulting\n"));
		return (give_mapping(lp, sdp, rw));
		} else {
		/*
		 * Owner must be writing to unlock page and there are waiters.
		 * other cases have been checked earlier.
		 * Release the lock, owner, and owners mappings
		 * As the owner is trying to write to the unlock page, leave
		 * it with a trashpage mapping and wake up the sleepers
		 */
		ASSERT((dhp == sdp->unlockseg) && (lp->sleepers != 0));
		DEBUGF(4, (CE_CONT, " owner fault on unlock seg w/ sleeper\n"));
		return (lock_giveup(lp, 1));
		}
	}

	ASSERT(owner != sdp);

	/*
	 * If old owner faulting on trash unlock mapping,
	 * load hat mappings to trash page
	 * RFE: non-owners should NOT be faulting on unlock mapping as they
	 * as first supposed to fault on the lock seg. We could give them
	 * a trash page or return error.
	 */
	if ((sdp->unlockseg == dhp) && (sdp->flag & TRASHPAGE)) {
		DEBUGF(4, (CE_CONT, " old owner reloads trash mapping\n"));
		return (devmap_load(sdp->unlockseg, lp->cookie, PAGESIZE,
		    DEVMAP_ACCESS, rw));
	}

	/*
	 * Non-owner faulting. Need to check current LOCK state.
	 *
	 * Before reading lock value in LOCK(lp), we must make sure that
	 * the owner cannot change its value before we change mappings
	 * or else we could end up either with a hung process
	 * or more than one process thinking they have the lock.
	 * We do that by unloading the owner's mappings
	 */
	DEBUGF(4, (CE_CONT, " owner loses mappings to check lock state\n"));
	err = devmap_unload(owner->lockseg, lp->cookie, PAGESIZE);
	err |= devmap_unload(owner->unlockseg, lp->cookie, PAGESIZE);
	if (err != 0)
		return (err);	/* unable to remove owner mapping */

	/*
	 * If lock is not held, then current owner mappings were
	 * unloaded above and we can give the lock to the new owner
	 */
	if (LOCK(lp) == 0) {
		DEBUGF(4, (CE_CONT,
		    "Free lock (%p): Giving mapping to new owner %d\n",
		    (void *)lp, ddi_get_pid()));
		return (give_mapping(lp, sdp, rw));
	}

	DEBUGF(4, (CE_CONT, "  lock held, sleeping\n"));

	/*
	 * A non-owning process tried to write (presumably to the lockpage,
	 * but it doesn't matter) but the lock is held; we need to sleep for
	 * the lock while there is an owner.
	 */

	lp->sleepers++;
	while ((owner = lp->owner) != NULL) {
		int rval;

		if ((lp->timeout == 0) || (owner->flag & SY_NOTIMEOUT)) {
			/*
			 * No timeout has been specified for this lock;
			 * we'll simply sleep on the condition variable.
			 */
			rval = cv_wait_sig(&lp->locksleep, &lp->mutex);
		} else {
			/*
			 * A timeout _has_ been specified for this lock. We need
			 * to wake up and possibly steal this lock if the owner
			 * does not let it go. Note that all sleepers on a lock
			 * with a timeout wait; the sleeper with the earliest
			 * timeout will wakeup, and potentially steal the lock
			 * Stealing the lock will cause a broadcast on the
			 * locksleep cv and thus kick the other timed waiters
			 * and cause everyone to restart in a new timedwait
			 */
			rval = cv_timedwait_sig(&lp->locksleep,
			    &lp->mutex, ddi_get_lbolt() + lp->timeout);
		}

		/*
		 * Timeout and still old owner - steal lock
		 * Force-Release lock and give old owner a trashpage mapping
		 */
		if ((rval == -1) && (lp->owner == owner)) {
			/*
			 * if any errors in lock_giveup, go back and sleep/retry
			 * If successful, will break out of loop
			 */
			cmn_err(CE_NOTE, "Process %d timed out on lock %d\n",
			    ddi_get_pid(), lp->cookie);
			(void) lock_giveup(lp, 1);
		} else if (rval == 0) { /* signal pending */
			cmn_err(CE_NOTE,
			    "Process %d signalled while waiting on lock %d\n",
			    ddi_get_pid(), lp->cookie);
			lp->sleepers--;
			return (FC_MAKE_ERR(EINTR));
		}
	}

	lp->sleepers--;
	/*
	 * Give mapping to this process and save a fault later
	 */
	return (give_mapping(lp, sdp, rw));
}

/*
 * Utility: give a valid mapping to lock and unlock pages to current process.
 * Caller responsible for unloading old owner's mappings
 */

static int
give_mapping(SegLock *lp, SegProc *sdp, uint_t rw)
{
	int err = 0;

	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(!((lp->owner == NULL) && (LOCK(lp) != 0)));
	/* give_mapping is always called with sdp in current process context */
	ASSERT(ID(sdp) == CURPROC_ID);

	/* remap any old trash mappings */
	if (sdp->flag & TRASHPAGE) {
		/* current owner should not have a trash mapping */
		ASSERT(sdp != lp->owner);

		DEBUGF(4, (CE_CONT,
		    "new owner %d remapping old trash mapping\n",
		    ddi_get_pid()));
		if ((err = devmap_umem_remap(sdp->unlockseg, winlock_dip,
		    lp->umem_cookie, 0, PAGESIZE, WINLOCK_PROT, 0, 0)) != 0) {
			/*
			 * unable to remap old trash page,
			 * abort before changing owner
			 */
			DEBUGF(4, (CE_CONT,
			    "aborting: error in umem_remap %d\n", err));
			return (err);
		}
		sdp->flag &= ~TRASHPAGE;
	}

	/* we have a new owner now */
	lp->owner = sdp;

	if ((err = devmap_load(sdp->lockseg, lp->cookie, PAGESIZE,
	    DEVMAP_ACCESS, rw)) != 0) {
		return (err);
	}
	DEBUGF(4, (CE_CONT, "new owner %d gets lock mapping", ddi_get_pid()));

	if (lp->sleepers) {
		/* Force unload unlock mapping if there are waiters */
		DEBUGF(4, (CE_CONT,
		    " lock has %d sleepers => remove unlock mapping\n",
		    lp->sleepers));
		err = devmap_unload(sdp->unlockseg, lp->cookie, PAGESIZE);
	} else {
		/*
		 * while here, give new owner a valid mapping to unlock
		 * page so we don't get called again.
		 */
		DEBUGF(4, (CE_CONT, " and unlock mapping\n"));
		err = devmap_load(sdp->unlockseg, lp->cookie, PAGESIZE,
		    DEVMAP_ACCESS, PROT_WRITE);
	}
	return (err);
}

/*
 * Unload owner's mappings, release the lock and wakeup any sleepers
 * If trash, then the old owner is given a trash mapping
 *	=> old owner held lock too long and caused a timeout
 */
static int
lock_giveup(SegLock *lp, int trash)
{
	SegProc *owner = lp->owner;

	DEBUGF(4, (CE_CONT, "winlock_giveup: lp=%p, owner=%p, trash %d\n",
	    (void *)lp, (void *)ID(lp->owner), trash));

	ASSERT(MUTEX_HELD(&lp->mutex));
	ASSERT(owner != NULL);

	/*
	 * owner loses lockpage/unlockpage mappings and gains a
	 * trashpage mapping, if needed.
	 */
	if (!trash) {
		/*
		 * We do not handle errors in devmap_unload in the !trash case,
		 * as the process is attempting to unmap/exit or otherwise
		 * release the lock. Errors in unloading the mapping are not
		 * going to affect that (unmap does not take error return).
		 */
		(void) devmap_unload(owner->lockseg, lp->cookie, PAGESIZE);
		(void) devmap_unload(owner->unlockseg, lp->cookie, PAGESIZE);
	} else {
		int err;

		if (err = devmap_unload(owner->lockseg, lp->cookie, PAGESIZE)) {
			/* error unloading lockseg mapping. abort giveup */
			return (err);
		}

		/*
		 * old owner gets mapping to trash page so it can continue
		 * devmap_umem_remap does a hat_unload (and does it holding
		 * the right locks), so no need to devmap_unload on unlockseg
		 */
		if ((err = devmap_umem_remap(owner->unlockseg, winlock_dip,
		    trashpage_cookie, 0, PAGESIZE, WINLOCK_PROT, 0, 0)) != 0) {
			/* error remapping to trash page, abort giveup */
			return (err);
		}
		owner->flag |= TRASHPAGE;
		/*
		 * Preload mapping to trash page by calling devmap_load
		 * However, devmap_load can only be called on the faulting
		 * process context and not on the owner's process context
		 * we preload only if we happen to be in owner process context
		 * Other processes will fault on the unlock mapping
		 * and be given a trash mapping at that time.
		 */
		if (ID(owner) == CURPROC_ID) {
			(void) devmap_load(owner->unlockseg, lp->cookie,
			    PAGESIZE, DEVMAP_ACCESS, PROT_WRITE);
		}
	}

	lp->owner = NULL;

	/* Clear the lock value in underlying page so new owner can grab it */
	LOCK(lp) = 0;

	if (lp->sleepers) {
		DEBUGF(4, (CE_CONT, "  waking up, lp=%p\n", (void *)lp));
		cv_broadcast(&lp->locksleep);
	}
	return (0);
}

/*
 * destroy all allocated memory.
 */

static void
lock_destroyall(void)
{
	SegLock	*lp, *lpnext;

	ASSERT(MUTEX_HELD(&winlock_mutex));
	ASSERT(lock_list == NULL);

	DEBUGF(1, (CE_CONT, "Lock list empty. Releasing free list\n"));
	for (lp = lock_free_list; lp != NULL; lp = lpnext) {
		mutex_enter(&lp->mutex);
		lpnext =  lp->next;
		ASSERT(lp->clients == NULL);
		ASSERT(lp->owner == NULL);
		ASSERT(lp->alloccount == 0);
		mutex_destroy(&lp->mutex);
		cv_destroy(&lp->locksleep);
		kmem_free(lp, sizeof (SegLock));
	}
	lock_free_list = NULL;
	next_lock = 0;
}


/* RFE: create mdb walkers instead of dump routines? */
static void
seglock_dump_all(void)
{
	SegLock	*lp;

	mutex_enter(&winlock_mutex);
	cmn_err(CE_CONT, "ID\tKEY\tNALLOC\tATTCH\tOWNED\tLOCK\tWAITER\n");

	cmn_err(CE_CONT, "Lock List:\n");
	for (lp = lock_list; lp != NULL; lp = lp->next) {
		mutex_enter(&lp->mutex);
		cmn_err(CE_CONT, "%d\t%d\t%u\t%c\t%c\t%c\t%d\n",
		    lp->cookie, lp->key, lp->alloccount,
		    lp->clients ? 'Y' : 'N',
		    lp->owner ? 'Y' : 'N',
		    lp->lockptr != 0 && LOCK(lp) ? 'Y' : 'N',
		    lp->sleepers);
		mutex_exit(&lp->mutex);
	}
	cmn_err(CE_CONT, "Free Lock List:\n");
	for (lp = lock_free_list; lp != NULL; lp = lp->next) {
		mutex_enter(&lp->mutex);
		cmn_err(CE_CONT, "%d\t%d\t%u\t%c\t%c\t%c\t%d\n",
		    lp->cookie, lp->key, lp->alloccount,
		    lp->clients ? 'Y' : 'N',
		    lp->owner ? 'Y' : 'N',
		    lp->lockptr != 0 && LOCK(lp) ? 'Y' : 'N',
		    lp->sleepers);
		mutex_exit(&lp->mutex);
	}

#ifdef DEBUG
	if (lock_debug < 3) {
		mutex_exit(&winlock_mutex);
		return;
	}

	for (lp = lock_list; lp != NULL; lp = lp->next) {
		SegProc	*sdp;

		mutex_enter(&lp->mutex);
		cmn_err(CE_CONT,
		    "lock %p, key=%d, cookie=%d, nalloc=%u, lock=%d, wait=%d\n",
		    (void *)lp, lp->key, lp->cookie, lp->alloccount,
		    lp->lockptr != 0 ? LOCK(lp) : -1, lp->sleepers);

		cmn_err(CE_CONT,
		    "style=%d, lockptr=%p, timeout=%ld, clients=%p, owner=%p\n",
		    lp->style, (void *)lp->lockptr, lp->timeout,
		    (void *)lp->clients, (void *)lp->owner);


		for (sdp = lp->clients; sdp != NULL; sdp = sdp->next) {
			cmn_err(CE_CONT, "  client %p%s, lp=%p, flag=%x, "
			    "process tag=%p, lockseg=%p, unlockseg=%p\n",
			    (void *)sdp, sdp == lp->owner ? " (owner)" : "",
			    (void *)sdp->lp, sdp->flag, (void *)ID(sdp),
			    (void *)sdp->lockseg, (void *)sdp->unlockseg);
		}
		mutex_exit(&lp->mutex);
	}
#endif
	mutex_exit(&winlock_mutex);
}

#include <sys/modctl.h>

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module.  This one is a driver */
	"Winlock Driver",	/* Name of the module */
	&winlock_ops,		/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,
	(void *)&modldrv,
	0,
	0,
	0
};

int
_init(void)
{
	int e;

	mutex_init(&winlock_mutex, NULL, MUTEX_DEFAULT, NULL);
	e = mod_install(&modlinkage);
	if (e) {
		mutex_destroy(&winlock_mutex);
	}
	return (e);
}


int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
	int	e;

	e = mod_remove(&modlinkage);
	if (e == 0) {
		mutex_destroy(&winlock_mutex);
	}
	return (e);
}