/* * 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 /* various type defn's */ #include #include /* various kernel limits */ #include #include #include /* defines kmem_alloc() */ #include /* defines cdevsw */ #include /* various file modes, etc. */ #include /* UIO stuff */ #include #include /* defines cred struct */ #include /* defines mmap(2) parameters */ #include /* defines S_IFCHR */ #include /* use cmn_err */ #include /* ddi stuff */ #include /* ddi stuff */ #include /* ddi stuff */ #include /* 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 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); }