/* $Header: $ */ #define _DDI_DKI 1 #define _SYSV4 1 /* * System V DDI/DKI compatible synchronisation functions * * This implements the synchronisation functions introduced in the System V * DDI/DKI multiprocessor edition for a simple uniprocessor. The locking * implementations given here are totally unsuitable for multiprocessor use. * * Some good multiprocessor lock algorithms can be found in: * "Synchronisation Without Contention" * John M. Mellor-Crummey & Michael L. Scott, * Proceedings 4th International Conference on Architectural Support for * Programming Languages and Operating Systems (ASPLOS IV) * 1991, ACM * * For an overview of techniques and a survey of software methods: * "Algorithms for Mutual Exclusion" * M. Raynal, * 1986, MIT Press ISBN 0-262-18119-3 * * $Log: $ */ /* *-IMPORTS: * <common/ccompat.h> * __CONST__ * __USE_PROTO__ * __ARGS () * <common/xdebug.h> * __LOCAL__ * <kernel/x86lock.h> * atomic_uchar_t * atomic_ushort_t * __atomic_uchar_t * ATOMIC_TEST_AND_SET_UCHAR () * ATOMIC_FETCH_AND_STORE_USHORT (); * ATOMIC_FETCH_UCHAR (); * ATOMIC_FETCH_USHORT (); * ATOMIC_CLEAR_UCHAR (); * ATOMIC_CLEAR_USHORT (); * <kernel/ddi_cpu.h> * ASSERT_BASE_LEVEL (); * ddi_cpu_data () * <kernel/v_proc.h> * plist_t * PROCESS_WOKEN * PROCESS_SIGNALLED * MAKE_SLEEPING () * WAKE_ONE () * WAKE_ALL () * PROC_HANDLE () * <sys/debug.h> * ASSERT () * <sys/types.h> * pl_t * uchar_t * uint_t * <sys/inline.h> * splhi () * splx () * splcmp () * <sys/cmn_err.h> * cmn_err () * <sys/kmem.h> * KM_SLEEP * KM_NOSLEEP */ #include <common/ccompat.h> #include <common/_tricks.h> #include <kernel/ddi_cpu.h> #include <kernel/ddi_lock.h> #include <kernel/v_proc.h> #include <sys/debug.h> #include <sys/types.h> #include <sys/inline.h> #include <sys/cmn_err.h> #include <sys/kmem.h> #include <stddef.h> #include <sys/ksynch.h> /* * The actual content definitions of the following data structures has been * kept totally private to this file in order to reduce the temptation of * allocating or accessing the definitions in other subsystems. * * Note that for efficiency considerations it might eventually become * necessary to export these definitions elsewhere to allow locks to be * statically defined (to break dependencies) or incorporated directly as * members of other data structures (to reduce allocation overheads). * (Of course, that means we have to define ...._INIT () routines). * * For now, we'll try very very hard to resist the temptation to optimize * too early and violate our encapsulation. */ /* * Configuration issue: DDI/DKI sleep locks don't really map well onto any * old-style Unix kernel functionality, nor does the "wake one" feature of * DDI/DKI synchronization variables. Sleep locks and synchronization * variables need to interface intimately with the kernel scheduling services * (in ways that were usually not anticipated by the original authors of those * services). This requires us to confront some key aspects of the difference * between the old and new styles to discover why we really *need* to * introduce a different way of doing things. * * Some features of the old sleep ()/wakeup () model: * a) These functions correspond exactly to the SV_WAIT[_SIG] () and * SV_BROADCAST () functions in the DDI/DKI, except that they do not * incorporate the basic-lock functionality of SV_WAIT[_SIG] (). * * b) Whether sleep was interruptible or not was related to the * "priority" indication passed to SV_WAIT[_SIG] () rather than being * orthogonal issue. This need not be vital, except that the * "priority" for sleep ()/wakeup () calls was expressed in magic, * highly implementation-dependent numbers. [*] * * c) On many systems, if sleep () was interrupted by a signal the result * was that the sleep was aborted by a non-local return. This is not * necessarily a problem *if* the caller was allowed to form a chain * of handlers to provide unwind-protection facilities. Sadly, this * is usually not the case. * * d) The ability to awaken only *one* process at a time was not * considered an issue; firstly, because contention was normally only * associated with actions involving long delay, such as I/O, and * because processes could only be run sequentially anyway. In other * words, even if contention happened, the fact that after a wakeup () * processes were run one at a time meant that often by the time a * process was run after a wakeup () the lock had been acquired *and* * released by any process with higher priority. * * [*] For instance, I have no idea what the three (!) different numbers that * have to be passed to a sleep () call in Coherent really do. There is no * internal documentation in the kernel source that clearly explicates the * roles that each number plays, nor what effect it has on the overall picture * of process scheduling. * * Each of features requires modification to the sleep ()/wakeup () model, as * outlined below: * a) In a uniprocessor kernel, sleep ()/wakeup () could function without * basic locks since the caller could gain equivalent protection by * simply manipulating the interrupt priority level, which would be * reset during the course of the dispatch process after the caller * had been safely put to sleep. In a multiprocessor, an unlock call * occuring *during* a call to sleep () might occur before the * process status had been changed, thus causing the caller to never * see the unlock... * * This suggests the following basic model for sleep locks: * * for (;;) { * acquire_basic_lock (); * if (resource_available ()) * break; * if (MAKE_SLEEPING () == PROCESS_SLEPT) { * release_basic_lock (); * RUN_NEXT (); // does not return * } * } * * where MAKE_SLEEPING () and RUN_NEXT () together form the same kind * of actions as sleep (), but broken into a pair that makes the * setjmp ()-style behaviour of rescheduling apparent and useful. In * particular, this keeps the desirable property that the low-level * scheduling system leaves the locking system in the hands of the * client code. * * Of course, there are various ways that this can be achieved. The * preferred style would be for MAKE_SLEEPING () to (optionally) test * for signals and to return some discriminating value. The values * returned from MAKE_SLEEPING () could be * * PROCESS_SLEPT slept * PROCESS_WOKEN woken normally * PROCESS_SIGNALLED woken by signal * * but for practical reasons this is difficult. While it is desirable * to expose at least part of the context-saving behaviour of the * inner scheduling layer, most of these facilities have the same * limitations as the setjmp () facility in that if the context which * directly called the context-save routine exits, all bets are off. * See (c) below for further discussion, and the final form will be * introduced at the end. * * b) This can be generally dealt with as-is by introducing appropriate * magic to map the abstract priorities into numbers for passing to * MAKE_SLEEPING (). * * c) The old style of signal handling is forbidden in the DDI/DKI * envinronment due to the locking style in use. In fact, the only * way that a driver can detect that a signal has been sent to a * process is by performing SV_WAIT_SIG () or SLEEP_LOCK_SIG () and * testing the return value. * * Moreover, signalling introduces one deficiency in the model * presented in a) above, namely that if a process is signalled, the * wakeup done in the signalling process knows nothing about the * locking system being used above (and in fact, if we layer on top of * old sleep ()/wakeup () code, our entire queueing system is ignored, * which would be easily fixable but for that locking problem). * * The simple solution would be to introduce an extra lock on part of * the process table than can ameliorate some of the problems, but * too many basic locks leads to hierarchy/ordering problems, and is * especially wasteful given that approriate row-level locking on * list headers should be completely sufficient without having to * introduce node-level locks. * * Essentially, we define the basic lock as being part of the list * head that sleeping processes are queued on and put a back-pointer * to the list head in the process table so that signal wakeup. This * complicates the generic sleep-lock and synchronization variable * functions a little, but we should be able to impose the semantics * we want (namely being able to guarantee deterministic ordering * on locks, which requires that the generic functions be accurately * able to determine the status of processes threaded on the sleep * queues). * * This definition of responsibilty for the queue head w.r.t. locking * behaviour allows us to fold the release_basic_lock () and * RUN_NEXT () behaviour into MAKE_SLEEPING (), which also helps to * solve the problem of dealing with the scope of context-save * introduced in (a). Note that we have to be careful about using the * process-table pointer to the list head since in order to find the * list head to lock it we have to read this information which is * potentially being modified as we access it. * * [ Note that it is possible to write a macro for MAKE_SLEEPING () * that does the context save in the caller's scope, but since it is * possible for a routine to *not* sleep at all (due to a pending * signal), that would mean it had to take the form: * FINALIZE_SLEEP (SAVE_CONTEXT (BEGIN_SLEEP (...)), ...) * in order to avoid introducing auxiliaries. ] * * d) The assumptions on which the old-style behaviour is based are no * longer valid on multiprocessing systems. Firstly, the more con- * current nature of multiprocessor systems increases the likelihood * of contention for locks. Secondly, broadcast wakeup is likely to * cause several of the woken processes to be run simultaneously on * different processors, resulting in timelines like this (measured * from a broadcast wakeup of processes waiting for a resource): * * CPU A : |-<acquire----------release>............... * CPU n : .|--*|--*|--*|--*...........|--<acquire --- * * where '|' indicates selection of a process, '-' indicates CPU time * consumed, and '*' indicates a process sleeping due to resource * unavailability, and '.' represents CPU time spent on unrelated * activity. Clearly, broadcast wakeup is not an optimal way of * implementing one-at-a-time lock functionality on a multiprocessor. * * Of course, implementing one-at-a-time wakeup facilities is not a * trivial matter. While awakening a single waiting process is fairly * simple, the matter of selecting *which* process to awaken is a * complex one that can have considerable impact on other kernel * systems such as the virtual memory system. In addition, there is * extra information available in the DDI/DKI system whose relation * to scheduling has not been studied (such as the number of locks * held by a context). Clearly, the internal layering of the DDI/DKI * implementation should be designed to make it easy to export this * information to the low-level kernel scheduling system. * * Of course, the worst case is that normal kernel scheduling policy * is completely supplanted in the DDI/DKI case. There looks to be * little alternative for the multiprocessor case, but it seems to * be a good idea to design the DDI/DKI implementation to support the * old-style broadcast functionality as a fallback. */ /* * Common inital part of a lock that is used to queue locks and hold the * statistics information. We use this definition to simplify the process of * keeping lists of all the allocated locks and to permit simple generic * operations on such lists. * * Downcast operators should be provided for the lock structures so that the * mapping from the node to the lock can be done in a fully portable fashion. */ typedef struct lock_node lnode_t; struct lock_node { lnode_t * ln_next; /* pointer to successor */ lnode_t * ln_prev; /* pointer to predecessor */ lkinfo_t * ln_lkinfo; /* statistics information */ }; /* * Internal structure of a basic lock */ struct __basic_lock { lnode_t bl_node; /* generic information */ #if _TICKET_LOCK atomic_ushort_t bl_next_ticket; /* next ticket number to be granted */ atomic_ushort_t bl_lock_holder; /* ticket number of lock holder */ #endif atomic_uchar_t bl_locked; /* is the basic lock acquired? */ pl_t bl_min_pl; /* minimum pl to be used in LOCK () */ uchar_t bl_hierarchy; /* acquisition order constraint */ }; /* * Internal structure of a read/write lock. * * Due to the shared nature of read locks, they are naturally expressed by * a count of outstanding readers maintained by atomic increment and * decrement operations. The exclusive (write) mode of such locks are best * expressed via the "ticket" mechanism if FIFO ordering is desired and/or * contention is to be minimized. * * The following implementation consists of a basic (exclusive) lock simply * augmented by a count; the use of a "ticket gate" test-and-set lock to * control access to the lock internal data reduces dependency on atomic * facilities such as fetch_and_increment and compare_and_swap that are not * widely available. If such facilities are available, consider using the * lock algorithm outlined in the paper referenced in the file header comment * above. */ struct readwrite_lock { lnode_t rw_node; /* generic information */ atomic_ushort_t rw_next_ticket; /* next ticket number to be granted */ atomic_ushort_t rw_lock_holder; /* ticket number of lock holder */ atomic_ushort_t rw_readers; atomic_uchar_t rw_locked; pl_t rw_min_pl; /* min pl to acquire the lock with */ uchar_t rw_hierarchy; /* acquisition order constraint */ }; /* * Internal structure of a synchronisation variable */ struct synch_var { lnode_t sv_node; /* generic information */ plist_t sv_plist [1]; /* list of blocked processes */ }; /* * Internal structure of a sleep lock. * * Note that the "sl_holder" member exists purely to support the debugging * SLEEP_LOCKOWNED () function, nothing else. What the System V documentation * fails to discuss is that sleep locks don't have to be owned by *any* * process. Sleep locks can be acquired at interrupt level via * SLEEP_TRYLOCK (), and released by interrupts as well. There doesn't appear * to be any mechanism to prevent locks being passed between processes by * various methods, and so on and so forth. * * Therefore, the flag members are the only members actually used by the * implementations of the sleep-lock functions, and the "sl_holder" member * may not be correct if the lock was acquired by an interrupt context. * * As discussed elsewhere, the main focus of activity for sleep locks is the * list of waiting processes, which contains a test-and-set lock that should * be used to control most aspects of lock state. */ struct sleep_lock { lnode_t sl_node; /* generic information */ atomic_uchar_t sl_locked; /* is sleep lock held? */ plist_t sl_plist [1]; /* process list */ _VOID * sl_holder; /* ID of process holding lock */ }; /* * Downcasting operators. Hopefully we won't need these. */ #define lnode_to_basic(n) ((lock_t *) ((char *) (n) - \ offsetof (lock_t, bl_node))) #define lnode_to_rw(n) ((rwlock_t *) ((char *) (n) - \ offsetof (rwlock_t, rw_node))) #define lnode_to_sv(n) ((sv_t *) ((char *) (n) - \ offsetof (sv_t, sv_node))) #define lnode_to_sleep(n) ((sleep_t *) ((char *) (n) - \ offsetof (sleep_t, sl_node))) /* * For debugging purposes, we want to keep lists of all the allocated locks. * * In the absence of atomic compare-and-swap operations, it's hard to define * good list-manipulation code, so we'll protect our list operations with * simple test-and-set locks. As usual on a uniprocessor these are still * useful for detecting potential inconsistency. * * There are no init or destroy methods/macros for this structure since there * are only the following static instances defined. */ __LOCAL__ struct lock_list { __CONST__ char * ll_name; /* name of list */ atomic_uchar_t ll_locked; /* single-thread list operations */ lnode_t * ll_head; /* head of list */ } basic_locks = { "basic lock" }, rw_locks = { "read/write lock" }, synch_vars = { "synchronisation variable" }, sleep_locks = { "sleep lock" }; #define LOCKLIST_LOCK(l,n) (TEST_AND_SET_LOCK ((l)->ll_locked, plhi, n)) #define LOCKLIST_UNLOCK(l,p) (ATOMIC_CLEAR_UCHAR ((l)->ll_locked),\ (void) splx (p)) /* * We must abstract the linkage between locks and memory allocation; the lock * system requires memory allocation services which require lock services... * certain definitions below (and presumably similar definitions in the memory * management system) can be used to resolve this dependency. In addition, * the abstraction provided can be used to decouple the provided systems (and * this aids portability) or to increase their coupling (to increase * performance). * * The definitions (which may be macros): * _lock_malloc () * _lock_free () * define an internal interface to the memory management system. If the memory * allocation system uses LOCK_ALLOC () to allocate the locks used to * coordinate access to the memory pool(s), the functions above will work as * expected even during the memory system's call to LOCK_ALLOC (), and will * coordinate access as necessary with other memory allocation interfaces * thereafter. */ __EXTERN_C_BEGIN__ _VOID * _lock_malloc __PROTO ((size_t size, int flag)); void _lock_free __PROTO ((_VOID * mem, size_t size)); __EXTERN_C_END__ /* * For now, we just map these functions onto the kmem_ interface and deal * with startup issues there. */ #define _lock_malloc(s,f) kmem_alloc (s, f) #define _lock_free(s,f) kmem_free (s, f) /* * This file-local function encapsulates the hierarchy-recording function that * deals with the bookkeeping necessary to ensure that locks are acquired * strictly in the order defined (to avoid deadlock). * * Due to the way in which the DDI/DKI locking functions are defined, it might * be possible on some architectures for a basic or read/write lock acquired * on one CPU to eventually be released on another. * * In a multi-processor system where this might be possible, it might be more * sensible to record the hierarchy information in some per-priority-level * fashion (as the DDI/DKI alludes to). Since this is currently not * necessary, we'll elect to design that ability later. */ #if __USE_PROTO__ __LOCAL__ void (LOCK_COUNT_HIERARCHY) (__lkhier_t hierarchy) #else __LOCAL__ void LOCK_COUNT_HIERARCHY __ARGS ((hierarchy)) __lkhier_t hierarchy; #endif { dcdata_t * dcdata = ddi_cpu_data (); pl_t prev_pl = splhi (); /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ dcdata->dc_hierarchy_cnt [hierarchy - __MIN_HIERARCHY__] ++; if (dcdata->dc_max_hierarchy < hierarchy) dcdata->dc_max_hierarchy = hierarchy; (void) splx (prev_pl); } /* * This file-local function is the dual to the above, for adjusting the * hierarchy information for a lock release. */ #if __USE_PROTO__ __LOCAL__ void (LOCK_FREE_HIERARCHY) (__lkhier_t hierarchy) #else __LOCAL__ void LOCK_FREE_HIERARCHY __ARGS ((hierarchy)) __lkhier_t hierarchy; #endif { dcdata_t * dcdata = ddi_cpu_data (); pl_t prev_pl = splhi (); /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ dcdata->dc_hierarchy_cnt [hierarchy - __MIN_HIERARCHY__] --; /* * Work out the lowest occupied priority level. */ do { if (dcdata->dc_hierarchy_cnt [dcdata->dc_max_hierarchy - __MIN_HIERARCHY__] > 0) break; } while (-- dcdata->dc_max_hierarchy >= __MIN_HIERARCHY__); (void) splx (prev_pl); } #if 0 /* * This file-local function takes care of recording debugging information and * statistics relating to lock acquisition. */ #if __USE_PROTO__ __LOCAL__ void (TRACE_BASIC_LOCK) (lock_t * lockp) #else __LOCAL__ void TRACE_BASIC_LOCK __ARGS ((lockp)) lock_t * lockp; #endif { /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ } /* * This file-local function takes care of undoing any data-structure changes * made by the above and recording additional statistics when a lock is * released */ #if __USE_PROTO__ __LOCAL__ void (UNTRACE_BASIC_LOCK) (lock_t * lockp) #else __LOCAL__ void UNTRACE_BASIC_LOCK __ARGS ((lockp)) lock_t * lockp; #endif { /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ } /* * This file-local function takes care of recording debugging information and * statistics relating to lock acquisition. */ #if __USE_PROTO__ __LOCAL__ void (TRACE_RW_LOCK) (rwlock_t * lockp) #else __LOCAL__ void TRACE_RW_LOCK __ARGS ((lockp)) rwlock_t * lockp; #endif { /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ } /* * This file-local function takes care of undoing any data-structure changes * made by the above and recording additional statistics when a lock is * released. */ #if __USE_PROTO__ __LOCAL__ void (UNTRACE_RW_LOCK) (rwlock_t * lockp) #else __LOCAL__ void UNTRACE_RW_LOCK __ARGS ((lockp)) rwlock_t * lockp; #endif { /* * We'll skip the usual paranoid ASSERT () tests since this is purely * a local function. */ } #else /* * Since all the above functions have empty bodies, we'll define empty macros * to get around the warnings. */ #define TRACE_BASIC_LOCK(l) #define UNTRACE_BASIC_LOCK(l) #define TRACE_RW_LOCK(l) #define UNTRACE_RW_LOCK(l) #endif /* * Simple common function to acquire a simple test-and-set lock. * * TEST_AND_SET_LOCK () uses the splraise () private function defined in * <sys/inline.h> to raise the processor priority level to "pl". This ensures * maximum safety in the use of this function, and certain DDI/DKI facilities * such as freezestr () require this behaviour. */ #if __USE_PROTO__ pl_t (TEST_AND_SET_LOCK) (__atomic_uchar_t locked, pl_t pl, __CONST__ char * name) #else pl_t TEST_AND_SET_LOCK __ARGS ((locked, pl, name)) __atomic_uchar_t locked; pl_t pl; __CONST__ char * name; #endif { for (;;) { pl_t prev_pl = splraise (pl); if (ATOMIC_TEST_AND_SET_UCHAR (locked) == 0) return prev_pl; /* all OK */ /* * While we spin for the lock, we can allow interrupts that * were permissible at entry to this routine. */ (void) splx (prev_pl); #if _UNIPROCESSOR cmn_err (CE_PANIC, "%s : test-and-set deadlock", name); #elif __TEST_AND_TEST__ /* * To reduce contention, we wait until the test-and-set lock * is free before attempting to re-acquire it. Of course, more * sophisticated backoff schemes might also help, even for * this approach. */ while (ATOMIC_FETCH_UCHAR (locked) != 0) /* DO NOTHING */; #endif } } #if 1 /* * Dump the basic-lock list. */ #if __USE_PROTO__ __LOCAL__ void dump_locks (int level) #else __LOCAL__ void dump_locks (level) int level; #endif { static int do_dump = 1; lnode_t * lnode = basic_locks.ll_head; if (! do_dump) { backtrace (0); return; } do_dump --; while (lnode != NULL) { lock_t * lock = lnode_to_basic (lnode); lnode = lnode->ln_next; if (level && ATOMIC_FETCH_UCHAR (lock->bl_locked) == 0) continue; cmn_err (CE_NOTE, "name %s locked %d", lock->bl_node.ln_lkinfo->lk_name, ATOMIC_FETCH_UCHAR (lock->bl_locked)); } } #else #define dump_locks(level) ((void) 0) #endif /* * When we are about to dispatch a process, check that the hierarchy level * is correct (ie, no basic or read/write locks are being held). */ #if __USE_PROTO__ void LOCK_DISPATCH (void) #else void LOCK_DISPATCH () #endif { if (ddi_cpu_data ()->dc_max_hierarchy > __MIN_HIERARCHY__) { dump_locks (0); backtrace (); } } /* * File-local function to initialise the generic part of a lock; this normally * enqueues the lock on a list. This function also has the responsibility of * allocating any needed statistics buffers, which requires that it be passed * the flag which indicates whether it is allowed to block or not. */ #if __USE_PROTO__ __LOCAL__ void (INIT_LNODE) (lnode_t * lnode, lkinfo_t * lkinfop, struct lock_list * list, int __NOTUSED (flag)) #else __LOCAL__ void INIT_LNODE __ARGS ((lnode, lkinfop, list, flag)) lnode_t * lnode; lkinfo_t * lkinfop; struct lock_list * list; int flag; #endif { pl_t prev_pl; ASSERT (lnode != NULL && list != NULL); /* * Here we allocate and initialise any needed statistics information, * currently nothing. */ lnode->ln_lkinfo = lkinfop; lnode->ln_prev = NULL; /* * Lock the list, enqueue the node, and unlock the list. */ prev_pl = LOCKLIST_LOCK (list, list->ll_name); if ((lnode->ln_next = list->ll_head) != NULL) lnode->ln_next->ln_prev = lnode; list->ll_head = lnode; LOCKLIST_UNLOCK (list, prev_pl); } /* * The dual to the above, this file-local function dequeues the node from the * given list of locks and frees any statistics buffer memory. */ #if __USE_PROTO__ __LOCAL__ void (FREE_LNODE) (lnode_t * lnode, struct lock_list * list) #else __LOCAL__ void FREE_LNODE __ARGS ((lnode, list)) lnode_t * lnode; struct lock_list * list; #endif { pl_t prev_pl; /* * We'll skip the paranoid assertions on this one, since the function * is file-local. * * Lock the list, dequeue the node, unlock the list. After that we * can free any statistics buffers. */ prev_pl = LOCKLIST_LOCK (list, list->ll_name); if (lnode->ln_prev == NULL) list->ll_head = lnode->ln_next; else lnode->ln_prev->ln_next = lnode->ln_next; if (lnode->ln_next != NULL) lnode->ln_next->ln_prev = lnode->ln_prev; LOCKLIST_UNLOCK (list, prev_pl); } /* *-STATUS: * DDI/DKI * *-NAME: * LOCK_ALLOC () Allocate a basic lock * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/kmem.h> * #include <sys/ksynch.h> * * lock_t * LOCK_ALLOC (uchar_t hierarchy, pl_t min_pl, * lkinfo_t * lkinfop, int flag); * *-ARGUMENTS: * hierarchy Hierarchy value which asserts the order in which this * lock will be acquired relative to other basic and * read/write locks. "hierarchy" must be within the range * of 1 through 32 inclusive and must be chosen such that * locks are normally acquired in order of increasing * "hierarchy" number. In other words, when acquiring a * basic lock using any function other than TRYLOCK (), * the lock being acquired must have a "hierarchy" value * that is strictly greater than the "hierarchy" values * associated with all locks currently held by the * calling context. * * Implementations of lock testing may differ in whether * they assume a separate range of "hierarchy" values for * each interrupt priority level or a single range that * spans all interrupt priority levels. In order to be * portable across different implementations, drivers * which may acquire locks at more than one interrupt * priority level should define the "hierarchy" among * those locks such that the "hierarchy" is strictly * increasing with increasing priority level (eg. if M * is the maximum "hierarchy" value defined for any lock * that may be acquired at priority level N, then M + 1 * should be the minimum hierarchy value for any lock * that may be acquired at any priority level greater * than N). * * min_pl Minimum priority level argument which asserts the * minimum priority leel that will be passed in with any * attempt to acquire this lock [see LOCK ()]. * Implementations which do not require that the * interrupt priority level be raised during lock * acquisition may choose not to enforce the "min_pl" * assertion. The valid values for this argument are as * follows: * * plbase Block no interrupts * pltimeout Block functions scheduled by itimeout * and dtimeout * pldisk Block disk device interrupts * plstr Block STREAMS interrupts * plhi Block all interrupts * * The notion of a "min_pl" assumes a defined order of * priority levels. The following partial order is * defined: * plbase < pltimeout <= pldisk, plstr <= plhi * * The ordering of pldisk and plstr relative to each * other is not defined. * * Setting a given priority level will block interrupts * associated with that level as well as all levels that * are defined to be less than or equal to the specified * level. In order to be portable a driver should not * acquire locks at different priority levels where the * relative order of those priority levels is not defined * above. * * The "min_pl" argument should specify a priority level * that would be sufficient to block out any interrupt * handler that might attempt to acquire this lock. In * addition, potential deadlock problems involving * multiple locks should be considered when defining the * "min_pl" value. For example, if the normal order of * acquisition of locks A and B (as defined by the lock * hierarchy) is to acquire A first and then B, lock B * should never be acquired at a priority level less * than the "min_pl" for lock A. Therefore, the "min_pl" * for lock B should be greater than or equal to the * "min_pl" for lock A. * * Note that the specification of a "min_pl" with a * LOCK_ALLOC () call does not actually cause any * interrupts to be blocked upon lock acquisition, it * simply asserts that subsequent LOCK () calls to * acquire this lock will pass in a priority level at * least as great as "min_pl". * * lkinfop Pointer to a lkinfo structure. The "lk_name" member of * the "lkinfo" structure points to a character string * defining a name that will be associated with the lock * for the purposes of statistics gathering. The name * should begin with the driver prefix and should be * unique to the lock or group of locks for which the * driver wishes to collect a uniquely identifiable set * of statistics (ie, if a given name is shared by a * group of locks, the statistics of the individual locks * within the group will not be uniquely identifiable). * There are no flags within the lk_flags member of the * lkinfo structure defined for use with LOCK_ALLOC (). * * A given lkinfo structure may be shared among multiple * basic locks and read/write locks but a lkinfo * structure may not be shared between a basic lock and * a sleep lock. The called must ensure that the lk_flags * and lk_pad members of the lkinfo structure are zeroed * out before passing it to LOCK_ALLOC (). * * flag Specifies whether the caller is willing to sleep * waiting for memory. If "flag" is set to KM_SLEEP, the * caller will sleep if necessary until sufficient memory * is available. If "flag" is set to KM_NOSLEEP, the * caller will not sleep, but LOCK_ALLOC () will return * NULL if sufficient memory is not immediately * available. * *-DESCRIPTION: * LOCK_ALLOC () dynamically allocates and initialises an instance of a * basic lock. The lock is initialised to the unlocked state. * *-RETURN VALUE: * Upon successful completion, LOCK_ALLOC () returns a pointer to the * newly allocated lock. If KM_NOSLEEP is specified and sufficient * memory is not immediately available, LOCK_ALLOC () returns a NULL * pointer. * *-LEVEL: * Base only if "flag" is set to KM_SLEEP. Base or Interrupt if "flag" is * set to KM_NOSLEEP. * *-NOTES: * May sleep if "flag" is set to KM_NOSLEEP. * * Driver-defined basic locks and read/write locks may be held across * calls to this function if "flag" is set to KM_NOSLEEP but may not be * held if "flag" is KM_SLEEP. * * Driver-defined sleep locks may be held across calls to this function * regardless of the value of "flag". * *-SEE ALSO: * LOCK (), LOCK_DEALLOC (), TRYLOCK (), UNLOCK (), lkinfo */ #define ASSERT_HIERARCHY_OK(h,lk) \ ASSERT ((lk) != NULL), \ ASSERT ((h) >= __MIN_HIERARCHY__ && \ (h) <= (((lk)->lk_flags & INTERNAL_LOCK) != 0 ? \ __MAX_HIERARCHY__ : __MAX_DDI_HIERARCHY__)) #if __USE_PROTO__ lock_t * (LOCK_ALLOC) (__lkhier_t hierarchy, pl_t min_pl, lkinfo_t * lkinfop, int flag) #else lock_t * LOCK_ALLOC __ARGS ((hierarchy, min_pl, lkinfop, flag)) __lkhier_t hierarchy; pl_t min_pl; lkinfo_t * lkinfop; int flag; #endif { lock_t * lockp; ASSERT_HIERARCHY_OK (hierarchy, lkinfop); ASSERT (splcmp (plbase, min_pl) <= 0 && splcmp (min_pl, plhi) <= 0); ASSERT (flag == KM_SLEEP || flag == KM_NOSLEEP); /* * Allocate and initialise the data, possibly waiting for enough * memory to become available. */ if ((lockp = (lock_t *) _lock_malloc (sizeof (* lockp), flag)) != NULL) { INIT_LNODE (& lockp->bl_node, lkinfop, & basic_locks, flag); lockp->bl_min_pl = min_pl; lockp->bl_hierarchy = hierarchy; ATOMIC_CLEAR_UCHAR (lockp->bl_locked); #if _TICKET_LOCK ATOMIC_CLEAR_USHORT (lockp->bl_next_ticket); ATOMIC_CLEAR_USHORT (lockp->bl_lock_holder); #endif } return lockp; } /* *-STATUS: * DDI/DKI * *-NAME: * LOCK_DEALLOC () Deallocate an instance of a basic lock. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void LOCK_DEALLOC (lock_t * lockp); * *-ARGUMENTS: * lockp Pointer to the basic lock to be deallocated. * *-DESCRIPTION: * LOCK_DEALLOC () deallocates the basic lock specified by "lockp". * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Attempting to deallocate a lock that is currently locked or is being * waited for is an error and will result in undefined behavior. * * Driver-defined basic locks (other than the one being deallocated), * read/write locks and sleep locks may be held across calls to this * function. * *-SEE ALSO: * LOCK (), LOCK_ALLOC (), TRYLOCK (), UNLOCK () */ #if __USE_PROTO__ void (LOCK_DEALLOC) (lock_t * lockp) #else void LOCK_DEALLOC __ARGS ((lockp)) lock_t * lockp; #endif { ASSERT (lockp != NULL); ASSERT (ATOMIC_FETCH_UCHAR (lockp->bl_locked) == 0); #if _TICKET_LOCK ASSERT (ATOMIC_FETCH_USHORT (lockp->bl_next_ticket) == ATOMIC_FETCH_USHORT (lockp->bl_lock_holder)); #endif /* * Remove from the list of all basic locks and free any statistics * buffer space before freeing the lock itself. */ FREE_LNODE (& lockp->bl_node, & basic_locks); _lock_free (lockp, sizeof (* lockp)); } /* *-STATUS: * DDI/DKI * *-NAME: * LOCK () Acquire a basic lock * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t LOCK (lock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the basic lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on which the lock is held, * portable drivers must specify a "pl" value that is * sufficient to block out any interrupt handler that * might attempt to acquire this lock. See the * description of the "min_pl" argument to LOCK_ALLOC () * for additional discussion. Implementations that do * not require that the interrupt priority level be * raised during lock acquisition may choose to ignore * this argument. * *-DESCRIPTION: * LOCK () sets the interrupt priority level in accordance with the * value specified by "pl" (if required by the implementation) and * acquires the lock specified by "lockp". If the lock is not currently * available, the caller will wait until the lock is available. It is * implementation-defined whether the caller will block during the wait. * Some implementations may cause the caller to spin for the duration of * the wait, while on others the caller may block at some point. * *-RETURN VALUE: * Upon acquiring the lock, LOCK () returns the previous interrupt * priority level. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Basic locks are not recursive. A call to LOCK () attempting to * acquire a lock that is already held by the calling context will * result in deadlock. * * Calls to LOCK () should honor the ordering defined by the lock * hierarchy [see LOCK_ALLOC ()] in order to avoid deadlock. * * Driver-defined sleep locks may be held across calls to this function. * * Driver-defined basic locks and read/write locks may be held across * calls to this function subject to the hierarchy and recursion * restrictions described above. * * When called from interrupt level, the "pl" argument must not specify * a priority below the level at which the interrupt handler is running. * *-SEE ALSO: * LOCK_ALLOC (), LOCK_DEALLOC (), TRYLOCK (), UNLOCK () */ #if __USE_PROTO__ pl_t (LOCK) (lock_t * lockp, pl_t pl) #else pl_t LOCK __ARGS ((lockp, pl)) lock_t * lockp; pl_t pl; #endif { pl_t prev_pl; #if _TICKET_LOCK ushort_t ticket_no; #endif ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->bl_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->bl_min_pl) >= 0); /* * On a uniprocessor, encountering a basic lock that is already * locked is *always* an error, even on machines with many different * interrupt priority levels. The hierarchy-assertion mechanism * cannot always deal with this, since TRYLOCK () can acquire locks * in a different order. * * On a multiprocessor, we can just spin here. Note that since LOCK () * is defined as requiring values of "pl" greater than the current * interrupt priority level, the use of splraise () by * TEST_AND_SET_LOCK () is legitimate. */ prev_pl = TEST_AND_SET_LOCK (lockp->bl_locked, pl, "LOCK"); #if _TICKET_LOCK /* * If we are working with a ticket-lock scheme, we now take a ticket * and release the test-and-set lock. After that, we can just wait for * our number to come up... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->bl_next_ticket); if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->bl_next_ticket, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the basic * lock protection isn't doing its job. */ cmn_err (CE_PANIC, "LOCK : Ticket-lock gate failure"); } ATOMIC_CLEAR_UCHAR (lockp->bl_locked); while (ATOMIC_FETCH_USHORT (lockp->bl_lock_holder) != ticket_no) { /* * At this point we might want to implement some form of * proportional backoff to reduce memory traffic. Later. * * Another hot contender for this spot is detecting failures * on other CPUs... */ #if _UNIPROCESSOR cmn_err (CE_PANIC, "LOCK : deadlock on ticket"); #endif } #endif /* _TICKET_LOCK */ /* * Now would be an appropriate time to check that the requested level * is >= the current level on entry. The architectures that we are * likely to target require this to be true, although some novel * schemes which reduce interrupt latency do not. * * Since the TEST_AND_SET_LOCK () function which set our priority * level uses splraise (), we are safe making this test at this late * stage. */ ASSERT (splcmp (pl, prev_pl) >= 0); /* * Test the lock-acquisition-hierarchy assertions. */ #if 1 if (ddi_cpu_data ()->dc_max_hierarchy >= lockp->bl_hierarchy) { cmn_err (CE_WARN, "Heirarchy problem; want %s (%d), have %d", lockp->bl_node.ln_lkinfo->lk_name, lockp->bl_hierarchy, ddi_cpu_data ()->dc_max_hierarchy); dump_locks (1); backtrace (0); } #else ASSERT (ddi_cpu_data ()->dc_max_hierarchy < lockp->bl_hierarchy); #endif LOCK_COUNT_HIERARCHY (lockp->bl_hierarchy); /* * Since it may be useful for post-mortem debugging to record some * information about the context which acquired the lock, we defer * to some generic recorder function or macro. * * Note that in general it does not appear to be possible to make * detailed assertions about the relation between the contexts in * which a lock is acquired and/or released. In particular, it * might be possible for a basic lock to be tied to some device * hardware-related operation where a lock might be acquired on one * CPU and released on another. * * In addition, any lock statistics are kept by this operation. */ TRACE_BASIC_LOCK (lockp); return prev_pl; } /* *-STATUS: * DDI/DKI * *-NAME: * TRYLOCK () Try to acquire a basic lock * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t TRYLOCK (lock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the basic lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on the processor on which the * lock is held, portable drivers must specify a "pl" * value that is sufficient to block out and interrupt * handler that might attempt to acquire this lock. See * the description of LOCK_ALLOC () for additional * discussion and a list of the valid values for "pl". * Implementations that do not require that the interrupt * priority level be raised during lock acquisition may * choose to ignore this argument. * *-DESCRIPTION: * If the lock specified by "lockp" is immediately available (can be * acquired without waiting) TRYLOCK () sets the interrupt priority level * in accordance with the value specified by "pl" (if required by the * implementation) and acquires the lock. If the lock is not immediately * available, the function returns without acquiring the lock. * *-RETURN VALUE: * If the lock is acquired, TRYLOCK () returns the previous interrupt * priority level ("plbase" - "plhi"). If the lock is not acquired the * value "invpl" is returned. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * TRYLOCK () may be used to acquire a lock in a different order from * the order defined by the lock hierarchy. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * * When called from interrupt level, the "pl" argument must not specify * a priority level below the level at which the interrupt handler is * running. * *-SEE ALSO: * LOCK (), LOCK_ALLOC (), LOCK_DEALLOC (), UNLOCK () */ #if __USE_PROTO__ pl_t (TRYLOCK) (lock_t * lockp, pl_t pl) #else pl_t TRYLOCK __ARGS ((lockp, pl)) lock_t * lockp; pl_t pl; #endif { pl_t prev_pl; #if _TICKET_LOCK ushort_t ticket_no; #endif ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->bl_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->bl_min_pl) >= 0); #if _TICKET_LOCK /* * If this is a ticket-lock, we test the ticket numbers to see * whether there is any reason to even try acquiring the test-and-set * lock that forms the ticket gate. * * We read the "lock holder" and "next ticket" entries in that * sequence to be pessimistic, since we can assume that other CPUs * might be looking at this... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->bl_lock_holder); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->bl_next_ticket)) return invpl; #endif /* * We block out interrupts at this point to allow the following * operations room to do their stuff in, since interrupts at an * inappropriate moment can cause deadlock... of course, if the * definition of the lock is such that interrupts can proceed, then * that's OK, since the lock acquisition is atomic. */ prev_pl = splx (pl); /* * Now would be an appropriate time to check that the * requested level is >= the current level on entry. Strictly * speaking, this does not have to be true if the processor is * currently at base level, but for now we'll discourage that * behaviour. */ ASSERT (splcmp (pl, prev_pl) >= 0); /* * Test to see whether the lock in question is already taken, and if * not, we take it. We don't spin if this is a ticket lock, since if * the basic lock is taken there is *no way* that the lock will be * free for us immediately. */ if (ATOMIC_TEST_AND_SET_UCHAR (lockp->bl_locked) == 0) { #if _TICKET_LOCK /* * If this is a ticket lock, now would be a good time to * check the ticket numbers to ensure that the lock *really* * is free. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->bl_next_ticket); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->bl_lock_holder)) { ATOMIC_CLEAR_UCHAR (lockp->bl_locked); goto try_failed; } if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->bl_next_ticket, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "TRYLOCK : Ticket-lock gate failure"); } /* * Now we have the lock, we can release the ticket-gate lock. */ ATOMIC_CLEAR_UCHAR (lockp->bl_locked); #endif /* _TICKET_LOCK */ /* * TRYLOCK () bypasses the hierarchy-assertion mechanism, * although we record the maximum acquired hierarchy level for * maximum strictness checking in inner LOCK () attempts. * * We also record debugging and statistics information here * with TRACE_BASIC_LOCK (). */ LOCK_COUNT_HIERARCHY (lockp->bl_hierarchy); TRACE_BASIC_LOCK (lockp); return prev_pl; } #if _TICKET_LOCK try_failed: #endif /* * We cannot acquire the lock, so reset the priority and exit with * the flag value. */ (void) splx (prev_pl); return invpl; } /* *-STATUS: * DDI/DKI * *-NAME: * UNLOCK () Release a basic lock. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * void UNLOCK (lock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the basic lock to be released. * * pl The interrupt priority level to be set after releasing * the lock. See the description of the "min_pl" argument * to LOCK_ALLOC () for a list of the valid values for * "pl". If lock calls are not being nested or if the * caller is unlocking in the reverse order that locks * were acquired, the "pl" argument will typically be the * value that was returned from the corresponding call to * acquire the lock. The caller may need to specify a * different value for "pl" if nested locks are being * released in some order other that the reverse order of * acquisition, so as to ensure that the interrupt * priority level is kept sufficiently high to block * interrupt code that might attempt to acquire locks * which are still held. Although portable drivers must * always specify an appropriate "pl" argument, * implementations which do not require that the * interrupt priority level be raised during lock * acquisition may choose to ignore this argument. * *-DESCRIPTION: * UNLOCK () releases the basic lock specified by "lockp" and then sets * the interrupt priority level in accordance with the value specified by * "pl" (if required by the implementation). * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * *-SEE ALSO: * LOCK (), LOCK_ALLOC (), LOCK_DEALLOC (), TRYLOCK () */ #if __USE_PROTO__ void (UNLOCK) (lock_t * lockp, pl_t pl) #else void UNLOCK __ARGS ((lockp, pl)) lock_t * lockp; pl_t pl; #endif { #if _TICKET_LOCK ushort_t ticket_no; #endif ASSERT (lockp != NULL); ASSERT (splcmp (plbase, pl) <= 0 && splcmp (pl, plhi) <= 0); /* * We assert that the lock is actually held by someone... in the case * of the ticket lock, we fetch the ticket number now since we don't * have an increment instruction and we'll need it later anyway. */ #if _TICKET_LOCK ticket_no = ATOMIC_FETCH_USHORT (lockp->bl_lock_holder); ASSERT (ticket_no != ATOMIC_FETCH_USHORT (lockp->bl_next_ticket)); #else ASSERT (ATOMIC_FETCH_UCHAR (lockp->bl_locked) != 0); #endif /* * Do whatever we need to do to undo the hierarchy-assertion and * debugging/statistics data structures. */ LOCK_FREE_HIERARCHY (lockp->bl_hierarchy); UNTRACE_BASIC_LOCK (lockp); /* * Now release the lock, either to the next ticket-holder or to * whoever gets in first, depending on the locking system. */ #if _TICKET_LOCK if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->bl_lock_holder, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then someone has * released the lock that we own! */ cmn_err (CE_PANIC, "UNLOCK : Ticket-lock sequence problem"); } #else ATOMIC_CLEAR_UCHAR (lockp->bl_locked); #endif /* * And lower out priority level to finish up. */ (void) splx (pl); } /* *-STATUS: * DDI/DKI * *-NAME: * RW_ALLOC () Allocate and initialize a read/write lock * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/kmem.h> * #include <sys/ksynch.h> * * rwlock_t * RW_ALLOC (uchar_t hierarchy, pl_t min_pl, * lkinfo_t * lkinfop, int flag); * *-ARGUMENTS: * hierarchy Hierarchy value which asserts the order in which this * lock will be acquired relative to other basic and * read/write locks. "hierarchy" must be within the range * of 1 through 32 inclusive and must be chosen such that * locks are normally acquired in order of increasing * "hierarchy" number. In other words, when acquiring a * basic lock using any function other than * RW_TRYRDLOCK () or RW_TRYWRLOCK () the lock being * acquired must have a "hierarchy" value that is * strictly greater than the "hierarchy" values * associated with all locks currently held by the * calling context. * * Implementations of lock testing may differ in whether * they assume a separate range of "hierarchy" values for * each interrupt priority level or a single range that * spans all interrupt priority levels. In order to be * portable across different implementations, drivers * which may acquire locks at more than one interrupt * priority level should define the "hierarchy" among * those locks such that the "hierarchy" is strictly * increasing with increasing priority level (eg. if M * is the maximum "hierarchy" value defined for any lock * that may be acquired at priority level N, then M + 1 * should be the minimum hierarchy value for any lock * that may be acquired at any priority level greater * than N). * * min_pl Minimum priority level argument which asserts the * minimum priority leel that will be passed in with any * attempt to acquire this lock [see RW_RDLOCK () and * RW_WRLOCK ()]. Implementations which do not require * that the interrupt priority level be raised during * lock acquisition may choose not to enforce the * "min_pl" assertion. The valid values for this * argument are as follows: * * plbase Block no interrupts * pltimeout Block functions scheduled by itimeout * and dtimeout * pldisk Block disk device interrupts * plstr Block STREAMS interrupts * plhi Block all interrupts * * The notion of a "min_pl" assumes a defined order of * priority levels. The following partial order is * defined: * plbase < pltimeout <= pldisk, plstr <= plhi * * The ordering of pldisk and plstr relative to each * other is not defined. * * Setting a given priority level will block interrupts * associated with that level as well as all levels that * are defined to be less than or equal to the specified * level. In order to be portable a driver should not * acquire locks at different priority levels where the * relative order of those priority levels is not defined * above. * * The "min_pl" argument should specify a priority level * that would be sufficient to block out any interrupt * handler that might attempt to acquire this lock. In * addition, potential deadlock problems involving * multiple locks should be considered when defining the * "min_pl" value. For example, if the normal order of * acquisition of locks A and B (as defined by the lock * hierarchy) is to acquire A first and then B, lock B * should never be acquired at a priority level less * than the "min_pl" for lock A. Therefore, the "min_pl" * for lock B should be greater than or equal to the * "min_pl" for lock A. * * Note that the specification of a "min_pl" with a * RW_ALLOC () call does not actually cause any * interrupts to be blocked upon lock acquisition, it * simply asserts that subsequent RW_RDLOCK () or * RW_WRLOCK () calls to acquire this lock will pass in a * priority level at least as great as "min_pl". * * lkinfop Pointer to a lkinfo structure. The lk_name member of * the lkinfo structure points to a character string * defining a name that will be associated with the lock * for the purposes of statistics gathering. The name * should begin with the driver prefix and should be * unique to the lock or group of locks for which the * driver wishes to collect a uniquely identifiable set * of statistics (ie, if a given name is shared by a * group of locks, the statistics of the individual locks * within the group will not be uniquely identifiable). * There are no flags within the lk_flags member of the * lkinfo structure defined for use with RW_ALLOC (). * * A given lkinfo structure may be shared among multiple * basic locks and read/write locks but a lkinfo * structure may not be shared between a basic lock and * a sleep lock. The called must ensure that the lk_flags * and lk_pad members of the lkinfo structure are zeroed * out before passing it to RW_ALLOC (). * * flag Specifies whether the caller is willing to sleep * waiting for memory. If "flag" is set to KM_SLEEP, the * caller will sleep if necessary until sufficient memory * is available. If "flag" is set to KM_NOSLEEP, the * caller will not sleep, but RW_ALLOC () will return * NULL if sufficient memory is not immediately * available. * *-DESCRIPTION: * RW_ALLOC () dynamically allocates and initialises an instance of a * read/write lock. The lock is initialised to the unlocked state. * *-RETURN VALUE: * Upon successful completion, RW_ALLOC () returns a pointer to the * newly allocated lock. If KM_NOSLEEP is specified and sufficient * memory is not immediately available, RW_ALLOC () returns a NULL * pointer. * *-LEVEL: * Base only if "flag" is set to KM_SLEEP. Base or Interrupt if "flag" is * set to KM_NOSLEEP. * *-NOTES: * May sleep if "flag" is set to KM_NOSLEEP. * * Driver-defined basic locks and read/write locks may be held across * calls to this function if "flag" is set to KM_NOSLEEP but may not be * held if "flag" is KM_SLEEP. * * Driver-defined sleep locks may be held across calls to this function * regardless of the value of "flag". * *-SEE_ALSO: * RW_DEALLOC (), RW_RDLOCK (), RW_TRYRDLOCK (), RW_TRYWRLOCK (), * RW_UNLOCK (), RW_WRLOCK (), lkinfo */ #if __USE_PROTO__ rwlock_t * (RW_ALLOC) (__lkhier_t hierarchy, pl_t min_pl, lkinfo_t * lkinfop, int flag) #else rwlock_t * RW_ALLOC __ARGS ((hierarchy, min_pl, lkinfop, flag)) __lkhier_t hierarchy; pl_t min_pl; lkinfo_t * lkinfop; int flag; #endif { rwlock_t * lockp; ASSERT_HIERARCHY_OK (hierarchy, lkinfop); ASSERT (splcmp (plbase, min_pl) <= 0 && splcmp (min_pl, plhi) <= 0); ASSERT (flag == KM_SLEEP || flag == KM_NOSLEEP); /* * Allocate and initialise the data, possibly waiting for enough * memory to become available. */ if ((lockp = (rwlock_t *) _lock_malloc (sizeof (* lockp), flag)) != NULL) { INIT_LNODE (& lockp->rw_node, lkinfop, & rw_locks, flag); lockp->rw_min_pl = min_pl; lockp->rw_hierarchy = hierarchy; ATOMIC_CLEAR_UCHAR (lockp->rw_locked); ATOMIC_CLEAR_USHORT (lockp->rw_readers); ATOMIC_CLEAR_USHORT (lockp->rw_next_ticket); ATOMIC_CLEAR_USHORT (lockp->rw_lock_holder); } return lockp; } /* *-STATUS: * DDI/DKI * *-NAME: * RW_DEALLOC () Deallocate an instance of a read/write lock. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void RW_DEALLOC (rwlock_t * lockp); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be deallocated. * *-DESCRIPTION: * RW_DEALLOC () deallocates the read/write lock specified by "lockp". * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Attempting to deallocate a lock that is currently locked or is being * waited for is an error and will result in undefined behavior. * * Driver-defined basic locks, read/write locks (other than the one being * deallocated), and sleep locks may be held across calls to this * function. * *-SEE_ALSO: * RW_ALLOC (), RW_RDLOCK (), RW_TRYRDLOCK (), RW_TRYWRLOCK (), * RW_UNLOCK (), RW_WRLOCK () */ #if __USE_PROTO__ void (RW_DEALLOC) (rwlock_t * lockp) #else void RW_DEALLOC __ARGS ((lockp)) rwlock_t * lockp; #endif { ASSERT (lockp != NULL); ASSERT (ATOMIC_FETCH_UCHAR (lockp->rw_locked) == 0); ASSERT (ATOMIC_FETCH_USHORT (lockp->rw_readers) == 0); ASSERT (ATOMIC_FETCH_USHORT (lockp->rw_next_ticket) == ATOMIC_FETCH_USHORT (lockp->rw_lock_holder)); /* * Remove from the list of all read/write locks and free any * statistics buffer space before freeing the lock itself. */ FREE_LNODE (& lockp->rw_node, & rw_locks); _lock_free (lockp, sizeof (* lockp)); } /* *-STATUS: * DDI/DKI * *-NAME: * RW_RDLOCK () Acquire a read/write lock in read mode. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t RW_RDLOCK (rwlock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on which the lock is held, * portable drivers must specify a "pl" value that is * sufficient to block out any interrupt handler that * might attempt to acquire this lock. See the * description of the "min_pl" argument to RW_ALLOC () * for additional discussion. Implementations that do * not require that the interrupt priority level be * raised during lock acquisition may choose to ignore * this argument. * *-DESCRIPTION: * RW_RDLOCK () sets the interrupt priority level in accordance with the * value specified by "pl" (if required by the implementation) and * acquires the lock specified by "lockp". If the lock is not currently * available, the caller will wait until the lock is available in read * mode. A read/write lock is available in read mode when the lock is * not held by any context or when the lock is held by one or more * readers and there are no waiting writers. It is implementation-defined * whether the caller will block during the wait. Some implementations * may cause the caller to spin for the duration of the wait, while on * others the caller may block at some point. * *-RETURN VALUE: * Upon acquiring the lock, RW_RDLOCK () returns the previous interrupt * priority level. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Read/write locks are not recursive. A call to RW_RDLOCK () attempting * to acquire a lock that is already held by the calling context may * result in deadlock. * * Calls to RD_RDLOCK () should honor the ordering defined by the lock * hierarchy [see RW_ALLOC ()] in order to avoid deadlock. * * Driver-defined sleep locks may be held across calls to this function. * * Driver-defined basic locks and read/write locks may be held across * calls to this function subject to the hierarchy and recursion * restrictions described above. * * When called from interrupt level, the "pl" argument must not specify * a priority below the level at which the interrupt handler is running. * *-SEE_ALSO: * RW_ALLOC (), RW_DEALLOC (), RW_TRYRDLOCK (), RW_TRYWRLOCK (), * RW_UNLOCK (), RW_WRLOCK () */ #if __USE_PROTO__ pl_t (RW_RDLOCK) (rwlock_t * lockp, pl_t pl) #else pl_t RW_RDLOCK __ARGS ((lockp, pl)) rwlock_t * lockp; pl_t pl; #endif { pl_t prev_pl; ushort_t ticket_no; ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->rw_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->rw_min_pl) >= 0); /* * On a uniprocessor, encountering a read/write lock that is already * locked is *always* an error, even on machines with many different * interrupt priority levels. The hierarchy-assertion mechanism * cannot always deal with this, since RW_TRYRDLOCK () can acquire * locks in a different order. * * On a multiprocessor, we can just spin here. */ for (;;) { /* * We block out merely the necessary interrupts at this point; * we don't need a blanket interrupt blockage since the lock * system works as atomically as possible. */ prev_pl = splx (pl); if (ATOMIC_TEST_AND_SET_UCHAR (lockp->rw_locked) == 0) { /* * Now we have acquired the test-and-set part, see * if there are any waiting writers preventing us * from acquiring a shared read lock. * * We read the "lock holder" and "next ticket" entries * in that sequence to be pessimistic, since we can * assume that other CPUs might be writing to this... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_lock_holder); if (ticket_no == ATOMIC_FETCH_USHORT (lockp->rw_next_ticket)) break; /* * The read/write lock is held exclusively, release * the test-and-set sub-lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); } /* * We can return the interrupt priority to whatever it was * upon entry to this routine since we can't acquire the * initial test-and-set lock. */ (void) splx (prev_pl); #if _UNIPROCESSOR cmn_err (CE_PANIC, "RW_RDLOCK : deadlock!"); #elif __TEST_AND_TEST__ /* * In order to reduce contention on the test-and-set part of * the read/write lock, we defer attempting to acquire that * lock until there appear to be no waiting writers, and until * the test-and-set lock is free before attempting to re- * acquire it. Of course, more sophisticated backoff schemes * might also help this approach. */ do { /* * We use this idiom to ensure that the lock holder * item is read before the next ticket item for * maximum pessimism. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_lock_holder); } while (ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_next_ticket) || ATOMIC_FETCH_UCHAR (lockp->rw_locked) != 0); #endif } /* * There are no waiting writers yet, so acquire the lock in read mode * by incrementing the count of readers. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_readers); if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_readers, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_RDLOCK : lock increment failure"); } #if 0 #if _UNIPROCESSOR if (ticket_no != 0) cmn_err (CE_WARN, "RW_RDLOCK : Recursive read-lock attempt"); #endif #endif /* * And allow other CPUs to try and acquire tickets or increment the * reader count by releasing the test-and-set lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); /* * Here we check and maintain the hierarchy assertions, and record * any required debugging/statistics information about the lock. */ LOCK_COUNT_HIERARCHY (lockp->rw_hierarchy); TRACE_RW_LOCK (lockp); /* * Now would be an appropriate time to check that the requested level * is >= the current level on entry. Strictly speaking, this does not * have to be true if the processor is currently at base level, but * for now we'll discourage that behaviour. */ ASSERT (splcmp (pl, prev_pl) >= 0); return prev_pl; } /* *-STATUS: * DDI/DKI * *-NAME: * RW_TRYRDLOCK () Try to acquire a read/write lock in read mode. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t RW_TRYRDLOCK (rwlock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on the processor on which the * lock is held, portable drivers must specify a "pl" * value that is sufficient to block out and interrupt * handler that might attempt to acquire this lock. See * the description of RW_ALLOC () for additional * discussion and a list of the valid values for "pl". * Implementations that do not require that the interrupt * priority level be raised during lock acquisition may * choose to ignore this argument. * *-DESCRIPTION: * If the lock specified by "lockp" is immediately available in read mode * (there is not a writer holding the lock and there are no waiting * writers) RW_TRYRDLOCK () sets the interrupt priority level in * accordance with the value specified by "pl" (if required by the * implementation) and acquires the lock in read mode. If the lock is not * immediately available in read mode, the function returns without * acquiring the lock. * *-RETURN VALUE: * If the lock is acquired, RW_TRYRDLOCK () returns the previous * interrupt priority level ("plbase" - "plhi"). If the lock is not * acquired the value "invpl" is returned. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * RW_TRYRDLOCK () may be used to acquire a lock in a different order * from the order defined by the lock hierarchy. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * * When called from interrupt level, the "pl" argument must not specify * a priority level below the level at which the interrupt handler is * running. * *-SEE_ALSO: * RW_ALLOC (), RW_DEALLOC (), RW_RDLOCK (), RW_TRYWRLOCK (), * RW_UNLOCK (), RW_WRLOCK () */ #if __USE_PROTO__ pl_t (RW_TRYRDLOCK) (rwlock_t * lockp, pl_t pl) #else pl_t RW_TRYRDLOCK __ARGS ((lockp, pl)) rwlock_t * lockp; pl_t pl; #endif { pl_t prev_pl; ushort_t ticket_no; ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->bl_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->rw_min_pl) >= 0); #ifdef __TEST_AND_TEST__ /* * We test the ticket numbers to see whether there is any reason to * even try acquiring the test-and-set lock that forms the ticket * gate. * * We read the "lock holder" and "next ticket" entries in that * sequence to be pessimistic, since we can assume that other CPUs * might be looking at this... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_lock_holder); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_next_ticket)) return invpl; #endif /* * We block out interrupts at this point to allow the following * operations room to do their stuff in, since interrupts at an * inappropriate moment can cause deadlock... of course, if the * definition of the lock is such that interrupts can proceed, then * that's OK, since the lock acquisition is atomic. */ prev_pl = splx (pl); /* * Now would be an appropriate time to check that the requested level * is >= the current level on entry. */ ASSERT (splcmp (pl, prev_pl) >= 0); /* * Test to see whether the lock in question is already taken, and if * not, we take it. We don't spin if this is a ticket lock, since if * the basic lock is taken there is *no way* that the lock will be * free for us immediately. */ if (ATOMIC_TEST_AND_SET_UCHAR (lockp->rw_locked) == 0) { /* * Check the ticket numbers to ensure that the lock *really* * is free. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_next_ticket); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_lock_holder)) { ATOMIC_CLEAR_UCHAR (lockp->rw_locked); goto try_failed; } /* * Now acquire the lock in read mode by incrementing the * count of readers. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_readers); if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_readers, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_TRYRDLOCK : Increment failure"); } /* * Now we have the lock, we can release the test-and-set lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); /* * RW_TRYRDLOCK () bypasses the hierarchy-assertion tests, * but we record the maximum acquired hierarchy level for * maximum strictness checking in inner lock attempts. * * We also record debugging and statistics information here * with TRACE_RW_LOCK (). */ LOCK_COUNT_HIERARCHY (lockp->rw_hierarchy); TRACE_RW_LOCK (lockp); /* * All done! Return successfully. */ return prev_pl; } try_failed: /* * We cannot acquire the lock, so reset the priority and exit with * the flag value. */ (void) splx (prev_pl); return invpl; } /* *-STATUS: * DDI/DKI * *-NAME: * RW_TRYWRLOCK () Try to acquire a read/write lock in write mode. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t RW_TRYWRLOCK (rwlock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on the processor on which the * lock is held, portable drivers must specify a "pl" * value that is sufficient to block out and interrupt * handler that might attempt to acquire this lock. See * the description of RW_ALLOC () for additional * discussion and a list of the valid values for "pl". * Implementations that do not require that the interrupt * priority level be raised during lock acquisition may * choose to ignore this argument. * *-DESCRIPTION: * If the lock specified by "lockp" is immediately available in write * mode (no context is holding the lock in read mode or write mode), * RW_TRYWRLOCK () sets the interrupt priority level in accordance with * the value specified by "pl" (if required by the implementation) and * acquires the lock in write mode. If the lock is not immediately * available in write mode, the function returns without acquiring the * lock. * *-RETURN VALUE: * If the lock is acquired, RW_TRYWRLOCK () returns the previous * interrupt priority level ("plbase" - "plhi"). If the lock is not * acquired the value "invpl" is returned. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * RW_TRYWRLOCK () may be used to acquire a lock in a different order * from the order defined by the lock hierarchy. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * * When called from interrupt level, the "pl" argument must not specify * a priority level below the level at which the interrupt handler is * running. * *-SEE_ALSO: * RW_ALLOC (), RW_DEALLOC (), RW_RDLOCK (), RW_TRYRDLOCK (), * RW_UNLOCK (), RW_WRLOCK () */ #if __USE_PROTO__ pl_t (RW_TRYWRLOCK) (rwlock_t * lockp, pl_t pl) #else pl_t RW_TRYWRLOCK __ARGS ((lockp, pl)) rwlock_t * lockp; pl_t pl; #endif { pl_t prev_pl; ushort_t ticket_no; ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->bl_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->rw_min_pl) >= 0); #ifdef __TEST_AND_TEST__ /* * We test the ticket numbers and reader count to see whether there * is any reason to even try acquiring the test-and-set lock that * forms the ticket gate. * * We read the "lock holder" and "next ticket" entries in that * sequence to be pessimistic, since we can assume that other CPUs * might be looking at this... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_lock_holder); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_next_ticket) || ATOMIC_FETCH_USHORT (lockp->rw_readers) != 0) return invpl; #endif /* * We block out interrupts at this point to allow the following * operations room to do their stuff in, since interrupts at an * inappropriate moment can cause deadlock... of course, if the * definition of the lock is such that interrupts can proceed, then * that's OK, since the lock acquisition is atomic. */ prev_pl = splx (pl); /* * Now would be an appropriate time to check that the requested level * is >= the current level on entry. */ ASSERT (splcmp (pl, prev_pl) >= 0); /* * Test to see whether the lock in question is already taken, and if * not, we take it. We don't spin if this is a ticket lock, since if * the basic lock is taken there is *no way* that the lock will be * free for us immediately. */ if (ATOMIC_TEST_AND_SET_UCHAR (lockp->rw_locked) == 0) { /* * Check the ticket numbers and the reader count to ensure * that the lock *really* is free. */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_next_ticket); if (ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_lock_holder) || ATOMIC_FETCH_USHORT (lockp->rw_readers) != 0) { ATOMIC_CLEAR_UCHAR (lockp->rw_locked); goto try_failed; } /* * Now acquire the lock in write mode by incrementing the * ticket counter. */ if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_next_ticket, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_TRYWRLOCK : Increment failure"); } /* * Now we have the lock, we can release the test-and-set lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); /* * RW_TRYWRLOCK () bypasses the hierarchy-assertion tests, * but we record the maximum acquired hierarchy level for * maximum strictness checking in inner lock attempts. * * We also record and debugging and statistics information * here with TRACE_RW_LOCK (). */ LOCK_COUNT_HIERARCHY (lockp->rw_hierarchy); TRACE_RW_LOCK (lockp); /* * All done! Return success... */ return prev_pl; } try_failed: /* * We cannot acquire the lock, so reset the priority and exit with * the flag value. */ (void) splx (prev_pl); return invpl; } /* *-STATUS: * DDI/DKI * *-NAME: * RW_UNLOCK () Release a read/write lock. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t RW_UNLOCK (rwlock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be released. * * pl The interrupt priority level to be set after releasing * the lock. See the description of the "min_pl" argument * to RW_ALLOC () for a list of the valid values for * "pl". If lock calls are not being nested or if the * caller is unlocking in the reverse order that locks * were acquired, the "pl" argument will typically be the * value that was returned from the corresponding call to * acquire the lock. The caller may need to specify a * different value for "pl" if nested locks are being * released in some order other that the reverse order of * acquisition, so as to ensure that the interrupt * priority level is kept sufficiently high to block * interrupt code that might attempt to acquire locks * which are still held. Although portable drivers must * always specify an appropriate "pl" argument, * implementations which do not require that the * interrupt priority level be raised during lock * acquisition may choose to ignore this argument. * *-DESCRIPTION: * RW_UNLOCK () releases the basic lock specified by "lockp" and then * sets the interrupt priority level in accordance with the value * specified by "pl" (if required by the implementation). * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * RW_ALLOC (), RW_DEALLOC (), RW_RDLOCK (), RW_TRYRDLOCK (), * RW_TRYWRLOCK (), RW_WRLOCK () */ #if __USE_PROTO__ void (RW_UNLOCK) (rwlock_t * lockp, pl_t pl) #else void RW_UNLOCK __ARGS ((lockp, pl)) rwlock_t * lockp; pl_t pl; #endif { ushort_t ticket_no; ASSERT (lockp != NULL); ASSERT (splcmp (plbase, pl) <= 0 && splcmp (pl, plhi) <= 0); /* * Undo whatever the hierarchy-assertion and debugging/statistics * mechanisms do. */ LOCK_FREE_HIERARCHY (lockp->rw_hierarchy); UNTRACE_RW_LOCK (lockp); /* * Now release the lock... since writers have to wait for all readers * to release the lock, if there any readers then we are releasing * in read mode. */ if (ATOMIC_FETCH_USHORT (lockp->rw_readers) != 0) { /* * In order to safely decrement the reader count, we have to * acquire the test-and-set lock in the absence of an * atomic decrement facility. */ while (ATOMIC_TEST_AND_SET_UCHAR (lockp->rw_locked) != 0) { #if _UNIPROCESSOR cmn_err (CE_PANIC, "RW_UNLOCK : deadlock!"); #elif __TEST_AND_TEST__ /* * To reduce contention, we wait until the test-and- * set lock is free before attempting to re-acquire * it. */ while (ATOMIC_FETCH_UCHAR (lockp->rw_locked) != 0) /* DO NOTHING */; #endif } ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_readers); if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_readers, ticket_no - 1) != ticket_no ) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_UNLOCK : Decrement failure"); } /* * Now free the test-and-set lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); } else if (ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_lock_holder), ticket_no != ATOMIC_FETCH_USHORT (lockp->rw_next_ticket)) { /* * Release the lock to the next ticket holder or the waiting * readers if there are no waiting ticket-holders. */ if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_lock_holder, ticket_no + 1) != ticket_no ) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_UNLOCK : Increment failure"); } } else cmn_err (CE_PANIC, "RW_UNLOCK : not locked"); /* * And lower out priority level to finish up. */ (void) splx (pl); } /* *-STATUS: * DDI/DKI * *-NAME: * RW_WRLOCK () Acquire a read/write lock in write mode. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * pl_t RW_WRLOCK (rwlock_t * lockp, pl_t pl); * *-ARGUMENTS: * lockp Pointer to the read/write lock to be acquired. * * pl The interrupt priority level to be set while the lock * is held by the caller. Because some implementations * require that interrupts that might attempt to acquire * the lock be blocked on which the lock is held, * portable drivers must specify a "pl" value that is * sufficient to block out any interrupt handler that * might attempt to acquire this lock. See the * description of the "min_pl" argument to RW_ALLOC () * for additional discussion. Implementations that do * not require that the interrupt priority level be * raised during lock acquisition may choose to ignore * this argument. * *-DESCRIPTION: * RW_WRLOCK () sets the interrupt priority level in accordance with the * value specified by "pl" (if required by the implementation) and * acquires the lock specified by "lockp". If the lock is not currently * available, the caller will wait until the lock is available in write * mode. A read/write lock is available in write mode when the lock is * not held by any context. It is implementation-defined whether the * caller will block during the wait. Some implementations may cause the * caller to spin for the duration of the wait, while on others the * caller may block at some point. * *-RETURN VALUE: * Upon acquiring the lock, RW_WRLOCK () returns the previous interrupt * priority level. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Read/write locks are not recursive. A call to RW_WRLOCK () attempting * to acquire a lock that is already held by the calling context may * result in deadlock. * * Calls to RD_WRLOCK () should honor the ordering defined by the lock * hierarchy [see RW_ALLOC ()] in order to avoid deadlock. * * Driver-defined sleep locks may be held across calls to this function. * * Driver-defined basic locks and read/write locks may be held across * calls to this function subject to the hierarchy and recursion * restrictions described above. * * When called from interrupt level, the "pl" argument must not specify * a priority below the level at which the interrupt handler is running. * *-SEE_ALSO: * RW_ALLOC (), RW_DEALLOC (), RW_RDLOCK (), RW_TRYRDLOCK (), * RW_TRYWRLOCK (), RW_UNLOCK () */ #if __USE_PROTO__ pl_t (RW_WRLOCK) (rwlock_t * lockp, pl_t pl) #else pl_t RW_WRLOCK __ARGS ((lockp, pl)) rwlock_t * lockp; pl_t pl; #endif { pl_t prev_pl; ushort_t ticket_no; ASSERT (lockp != NULL); ASSERT (splcmp (pl, plhi) <= 0); /* * Enforce minimum-priority assertion, pl >= lockp->rw_min_pl. Note * that splcmp () abstracts subtraction-for-comparison of priority * levels, which explains the form of the assertion. */ ASSERT (splcmp (pl, lockp->rw_min_pl) >= 0); /* * On a uniprocessor, encountering a read/write lock that is already * locked is *always* an error, even on machines with many different * interrupt priority levels. The hierarchy-assertion mechanism * cannot always deal with this, since RW_TRYRDLOCK () can acquire * locks in a different order. * * On a multiprocessor, we can just spin here. Note that since LOCK () * is defined as requiring values of "pl" greater than the current * interrupt priority level, the use of splraise () by * TEST_AND_SET_LOCK () is legitimate. */ prev_pl = TEST_AND_SET_LOCK (lockp->rw_locked, pl, "RW_WRLOCK"); /* * Take a ticket... */ ticket_no = ATOMIC_FETCH_USHORT (lockp->rw_next_ticket); if (ATOMIC_FETCH_AND_STORE_USHORT (lockp->rw_next_ticket, ticket_no + 1) != ticket_no) { /* * If we didn't read the same number twice, then the * test-and-set lock protection isn't doing its job. */ cmn_err (CE_PANIC, "RW_WRLOCK : ticket increment failure"); } /* * Allow other CPUs access to the lock data structures by releasing * the test-and-set lock. */ ATOMIC_CLEAR_UCHAR (lockp->rw_locked); /* * Now, let's wait for our number to come up and for all the readers * to release their shared locks. */ while (ATOMIC_FETCH_USHORT (lockp->rw_lock_holder) != ticket_no || ATOMIC_FETCH_USHORT (lockp->rw_readers) != 0) { /* * At this point we might want to implement some form of * proportional backoff to reduce memory traffic. Later. * * Another hot contender for this spot is detecting failures * on other CPUs... */ #if _UNIPROCESSOR cmn_err (CE_PANIC, "RW_WRLOCK : deadlock on ticket"); #endif } /* * Now would be an appropriate time to check that the requested level * is >= the current level on entry. Strictly speaking, this does not * have to be true if the processor is currently at base level, but * for now we'll discourage that behaviour. */ ASSERT (splcmp (pl, prev_pl) >= 0); /* * Test the lock-acquisition-hierarchy assertions. */ ASSERT (ddi_cpu_data ()->dc_max_hierarchy < lockp->rw_hierarchy); LOCK_COUNT_HIERARCHY (lockp->rw_hierarchy); /* * Since it may be useful for post-mortem debugging to record some * information about the context which acquired the lock, we defer * to some generic recorder function or macro. * * Note that in general it does not appear to be possible to make * detailed assertions about the relation between the contexts in * which a lock is acquired and/or released. In particular, it * might be possible for a basic lock to be tied to some device * hardware-related operation where a lock might be acquired on one * CPU and released on another. * * In addition, any lock statistics are kept by this operation. */ TRACE_RW_LOCK (lockp); return prev_pl; } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_ALLOC () Allocate and initialize a sleep lock. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/kmem.h> * #include <sys/ksynch.h> * * sleep_t * SLEEP_ALLOC (int arg, lkinfo_t * lkinfop, int flag); * *-ARGUMENTS: * arg Placeholder for future use. "arg" must be equal to * zero. * * * lkinfop Pointer to a lkinfo structure. The lk_name member of * the lkinfo structure points to a character string * defining a name that will be associated with the lock * for the purposes of statistics gathering. The name * should begin with the driver prefix and should be * unique to the lock or group of locks for which the * driver wishes to collect a uniquely identifiable set * of statistics (ie, if a given name is shared by a * group of locks, the statistics of the individual locks * within the group will not be uniquely identifiable). * * The only bit flag currently specified within the * "lk_flags" member of the lkinfo structure is the * LK_NOSTATS flag, which specifies that statistics are * not to be collected for this particular lock. * * A given lkinfo structure may be shared among multiple * sleep but a lkinfo structure may not be shared between * a sleep lock and a basic or read/write lock. The * called must ensure that the "lk_pad" member of the * "lkinfo" structure is zeroed out before passing it to * SLEEP_ALLOC (). * * flag Specifies whether the caller is willing to sleep * waiting for memory. If "flag" is set to KM_SLEEP, the * caller will sleep if necessary until sufficient memory * is available. If "flag" is set to KM_NOSLEEP, the * caller will not sleep, but SLEEP_ALLOC () will return * NULL if sufficient memory is not immediately * available. * *-DESCRIPTION: * SLEEP_ALLOC () dynamically allocates and initialises an instance of a * sleep lock. The lock is initialised to the unlocked state. * *-RETURN VALUE: * Upon successful completion, SLEEP_ALLOC () returns a pointer to the * newly allocated lock. If KM_NOSLEEP is specified and sufficient * memory is not immediately available, SLEEP_ALLOC () returns a NULL * pointer. * *-LEVEL: * Base only if "flag" is set to KM_SLEEP. Base or Interrupt if "flag" is * set to KM_NOSLEEP. * *-NOTES: * May sleep if "flag" is set to KM_NOSLEEP. * * Driver-defined basic locks and read/write locks may be held across * calls to this function if "flag" is set to KM_NOSLEEP but may not be * held if "flag" is KM_SLEEP. * * Driver-defined sleep locks may be held across calls to this function * regardless of the value of "flag". * *-SEE_ALSO: * SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), * SLEEP_LOCKAVAIL (), SLEEP_LOCKOWNED (), SLEEP_TRYLOCK (), * SLEEP_UNLOCK (), lkinfo */ #if __USE_PROTO__ sleep_t * (SLEEP_ALLOC) (int arg, lkinfo_t * lkinfop, int flag) #else sleep_t * SLEEP_ALLOC __ARGS ((arg, lkinfop, flag)) int arg; lkinfo_t * lkinfop; int flag; #endif { sleep_t * lockp; ASSERT (arg == 0); ASSERT (flag == KM_SLEEP || flag == KM_NOSLEEP); ASSERT (lkinfop != NULL); /* * Allocate and initialise the data, possibly waiting for enough * memory to become available. */ if ((lockp = (sleep_t *) _lock_malloc (sizeof (* lockp), flag)) != NULL) { INIT_LNODE (& lockp->sl_node, lkinfop, & sleep_locks, flag); PLIST_INIT (lockp->sl_plist); } return lockp; } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_DEALLOC () Deallocate an instance of a sleep lock. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void SLEEP_DEALLOC (sleep_t * lockp); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be deallocated. * *-DESCRIPTION: * SLEEP_DEALLOC () deallocates the sleep lock specified by "lockp". * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Attempting to deallocate a lock that is currently locked or is being * waited for is an error and will result in undefined behavior. * * Driver-defined basic locks, read/write locks, and sleep locks (other * than the one being deallocated) may be held across calls to this * function. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), SLEEP_LOCKAVAIL (), * SLEEP_LOCKOWNED (), SLEEP_TRYLOCK (), SLEEP_UNLOCK () */ #if __USE_PROTO__ void (SLEEP_DEALLOC) (sleep_t * lockp) #else void SLEEP_DEALLOC __ARGS ((lockp)) sleep_t * lockp; #endif { ASSERT (lockp != NULL); /* * Remove from the list of all sleep locks and free any statistics * buffer space before freeing the lock itself. */ PLIST_DESTROY (lockp->sl_plist); FREE_LNODE (& lockp->sl_node, & sleep_locks); _lock_free (lockp, sizeof (* lockp)); } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_LOCK () Acquire a sleep lock. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void SLEEP_LOCK (sleep_t * lockp, int priority); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be acquired. * * priority A hint to the scheduling policy as to the relative * priority the caller wishes to be assigned while * running in the kernel after waking up. The valid * values for this argument are as follows: * * pridisk Priority appropriate for disk driver * prinet Priority appropriate for network driver. * pritty Priority appropriate for terminal driver. * pritape Priority appropriate for tape driver. * prihi High priority. * primed Medium priority. * prilo Low priority. * * Drivers may use these values to request a priority * appropriate to a given type of device or to request a * priority that is high, medium or low relative to other * activities within the kernel. * * It is also permissible to specify positive or negative * offsets from the values defined above. Positive * offsets result in favourable priority. The maximum * allowable offset in all cases is 3 (eg. pridisk+3 * and pridisk-3 are valid values by pridisk+4 and * pridisk-4 are not valid). Offsets can be useful in * defining the relative importance of different locks or * resources that may be hald by a given driver. In * general, a higher relative priority should be used * when the caller is attempting to acquire a highly- * contended lock or resource, or when the caller is * already holding one or more locks or kernel resources * upon entry to SLEEP_LOCK (). * * The exact semantics of the "priority" argument is * specific to the scheduling class of the caller, and * some scheduling classes may choose to ignore the 8 argument for the purposes of assigning a scheduling * priority. * *-DESCRIPTION: * SLEEP_LOCK () acquires the sleep lock specified by "lockp". If the * lock is not immediately available, the caller is put to sleep (the * caller's execution is suspended and other processes may be scheduled) * until the lock becomes available to the caller, at which point the * caller wakes up and returns with the lock held. * * The caller will not be interrupted by signals while sleeping inside * SLEEP_LOCK (). * *-RETURN VALUE: * None. * *-LEVEL: * Base level only. * *-NOTES: * May sleep. * * Sleep locks are not recursive. A call to SLEEP_LOCK () attempting to * acquire a lock that is currently held by the calling context will * result in deadlock. * * Driver-defined basic locks and read/write locks may not be held across * calls to this function. * * Driver-defined sleep locks may be held across calls to this function * subject to the recursion restrictions described above. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK_SIG (), * SLEEP_LOCKAVAIL (), SLEEP_LOCKOWNED (), SLEEP_TRYLOCK (), * SLEEP_UNLOCK () */ #if __USE_PROTO__ void (SLEEP_LOCK) (sleep_t * lockp, int priority) #else void SLEEP_LOCK __ARGS ((lockp, priority)) sleep_t * lockp; int priority; #endif { pl_t prev_pl; ASSERT (lockp != NULL); ASSERT_BASE_LEVEL (); /* * Take the path of least resistance; if the sleep lock is not * currently held, then just acquire it and get out. */ if (ATOMIC_TEST_AND_SET_UCHAR (lockp->sl_locked) == 0) { /* * We successfully acquired it! Just leave after writing in * the info for SLEEP_LOCKAVAIL (). */ lockp->sl_holder = PROC_HANDLE (); return; } /* * OK, we do it the hard way. * * The sleep lock might have been unlocked while we waited to lock * the process list, so we retest the item. */ prev_pl = PLIST_LOCK (lockp->sl_plist, "SLEEP_LOCK"); if (ATOMIC_TEST_AND_SET_UCHAR (lockp->sl_locked) != 0) { /* * No, we need to wait. Note the cast to void... we assume * that MAKE_SLEEPING () will filter out all improper attempts * to wake us. * * We assert that whoever woke us up will have left the sleep * locked in the "locked" state for us. While *we* won't have * the process-list locked, someone else might so we can't * make an assertion on that. */ (void) MAKE_SLEEPING (lockp->sl_plist, priority, SLEEP_NO_SIGNALS); ASSERT (lockp->sl_holder == PROC_HANDLE ()); ASSERT (ATOMIC_FETCH_UCHAR (lockp->sl_locked) != 0); (void) splx (prev_pl); } else { /* * Write the owner information for SLEEP_LOCKOWNED () and * release the test-and-set lock on the process list. */ lockp->sl_holder = PROC_HANDLE (); PLIST_UNLOCK (lockp->sl_plist, prev_pl); } } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_LOCKAVAIL () Query whether a sleep lock is available. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * bool_t SLEEP_LOCKAVAIL (sleep_t * lockp); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be queried. * *-DESCRIPTION: * SLEEP_LOCKAVAIL () returns an indication of whether the sleep lock * specified by "lockp" is currently available. * * The state of the lock may change and the value returned may no longer * be valid by the time the caller sees it. The caller is expected to * understand that this is "stale data" and is either using it as a * heuristic or has arranged for the data to be meaningful by other * means. * *-RETURN VALUE: * SLEEP_LOCKAVAIL () returns TRUE (a non-zero value) if the lock was * available or FALSE (zero) if the lock was not available. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), * SLEEP_LOCKOWNED (), SLEEP_TRYLOCK (), SLEEP_UNLOCK () */ #if __USE_PROTO__ bool_t (SLEEP_LOCKAVAIL) (sleep_t * lockp) #else bool_t SLEEP_LOCKAVAIL __ARGS ((lockp)) sleep_t * lockp; #endif { ASSERT (lockp != NULL); return ATOMIC_FETCH_UCHAR (lockp->sl_locked) == 0; } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_LOCKOWNED () Query whether a sleep lock is held by the caller. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * bool_t SLEEP_LOCKOWNED (sleep_t * lockp); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be queried. * *-DESCRIPTION: * SLEEP_LOCKOWNED () returns an indication of whether the sleep lock * specified by "lockp" is held by the calling context. * * SLEEP_LOCKOWNED () is intended for use only within ASSERT () * expressions [see ASSERT ()] and other code that is conditionally * compiled under the DEBUG compilation option. The SLEEP_LOCKOWNED () * function is only defined under the DEBUG compilation option, and * therefore calls to SLEEP_LOCKOWNED () will not compile when DEBUG is * not defined. * *-RETURN VALUE: * SLEEP_LOCKOWNED () returns TRUE (a non-zero value) if the lock is * currently held by the calling context or FALSE (zero) if the lock is * not currently held by the calling context. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), * SLEEP_LOCKAVAIL (), SLEEP_TRYLOCK (), SLEEP_UNLOCK () */ #if __USE_PROTO__ bool_t (SLEEP_LOCKOWNED) (sleep_t * lockp) #else bool_t SLEEP_LOCKOWNED __ARGS ((lockp)) sleep_t * lockp; #endif { ASSERT (lockp != NULL); return lockp->sl_holder == PROC_HANDLE (); } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_LOCK_SIG () Acquire a sleep lock. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * bool_t SLEEP_LOCK_SIG (sleep_t * lockp, int priority); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be acquired. * * priority A hint to the scheduling policy as to the relative * priority the caller wishes to be assigned while * running in the kernel after waking up. The valid * values for this argument are as follows: * * pridisk Priority appropriate for disk driver * prinet Priority appropriate for network driver. * pritty Priority appropriate for terminal driver. * pritape Priority appropriate for tape driver. * prihi High priority. * primed Medium priority. * prilo Low priority. * * Drivers may use these values to request a priority * appropriate to a given type of device or to request a * priority that is high, medium or low relative to other * activities within the kernel. * * It is also permissible to specify positive or negative * offsets from the values defined above. Positive * offsets result in favourable priority. The maximum * allowable offset in all cases is 3 (eg. pridisk+3 * and pridisk-3 are valid values by pridisk+4 and * pridisk-4 are not valid). Offsets can be useful in * defining the relative importance of different locks or * resources that may be hald by a given driver. In * general, a higher relative priority should be used * when the caller is attempting to acquire a highly- * contended lock or resource, or when the caller is * already holding one or more locks or kernel resources * upon entry to SLEEP_LOCK_SIG (). * * The exact semantics of the "priority" argument to the * scheduling class of the caller, and some scheduling * classes may choose to ignore the argument for the * purposes of assigning a scheduling priority. * *-DESCRIPTION: * SLEEP_LOCK_SIG () acquires the sleep lock specified by "lockp". If the * lock is not immediately available, the caller is put to sleep (the * caller's execution is suspended and other processes may be scheduled) * until the lock becomes available to the caller, at which point the * caller wakes up and returns with the lock held. * * SLEEP_LOCK_SIG () may be interrupted by a signal, in which case it may * return early without acquiring the lock. * * If the function is interrupted by a job control stop signal (eg * SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU) which results in the caller * entering a stopped state, the SLEEP_LOCK_SIG () function will * transparently retry the lock operation upon continuing (the caller * will not return without the lock). * * If the function is interrupted by a signal other than a job control * signal, or by a job control signal that does not result in the caller * stopping (because the signal has a non-default disposition), the * SLEEP_LOCK_SIG () function will return early without acquiring the * lock. * *-RETURN VALUE: * SLEEP_LOCK_SIG () returns TRUE (a non-zero value) if the lock is * successfully acquired or FALSE (zero) if the function returned early * because of a signal. * *-LEVEL: * Base level only. * *-NOTES: * May sleep. * * Sleep locks are not recursive. A call to SLEEP_LOCK_SIG () attempting * to acquire a lock that is currently held by the calling context will * result in deadlock. * * Driver-defined basic locks and read/write locks may not be held across * calls to this function. * * Driver-defined sleep locks may be held across calls to this function * subject to the recursion restrictions described above. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCKAVAIL (), * SLEEP_LOCKOWNED (), SLEEP_TRYLOCK (), SLEEP_UNLOCK (), signals */ #if __USE_PROTO__ bool_t (SLEEP_LOCK_SIG) (sleep_t * lockp, int priority) #else bool_t SLEEP_LOCK_SIG __ARGS ((lockp, priority)) sleep_t * lockp; int priority; #endif { pl_t prev_pl; ASSERT (lockp != NULL); ASSERT_BASE_LEVEL (); /* * Take the path of least resistance; if the sleep lock is not * currently held, then just acquire it and get out. */ if (ATOMIC_TEST_AND_SET_UCHAR (lockp->sl_locked) == 0) { /* * We successfully acquired it! Just leave after writing in * the info for SLEEP_LOCKAVAIL (). */ lockp->sl_holder = PROC_HANDLE (); return TRUE; } /* * OK, we do it the hard way. * * The sleep lock might have been unlocked while we waited to lock * the process list, so we retest that member. */ prev_pl = PLIST_LOCK (lockp->sl_plist, "SLEEP_LOCK"); if (ATOMIC_TEST_AND_SET_UCHAR (lockp->sl_locked) != 0) { /* * No, we need to wait. */ if (MAKE_SLEEPING (lockp->sl_plist, priority, SLEEP_INTERRUPTIBLE) == PROCESS_SIGNALLED) { /* * We were signalled. MAKE_SLEEPING () will have * released the test-and-set lock on the process list, * so we just have to reset the interrupt priority * level and return an indication to the caller. */ (void) splx (prev_pl); return FALSE; } /* * We assert that whoever woke us up will have left the sleep * locked in the "locked" state for us. While *we* won't have * the process-list locked, someone else might so we can't * make an assertion on that. */ ASSERT (lockp->sl_holder == PROC_HANDLE ()); ASSERT (ATOMIC_FETCH_UCHAR (lockp->sl_locked) != 0); (void) splx (prev_pl); } else { /* * Write the owner information for SLEEP_LOCKAVAIL () and * release the test-and-set lock on the process list. */ lockp->sl_holder = PROC_HANDLE (); PLIST_UNLOCK (lockp->sl_plist, prev_pl); } return TRUE; } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_TRYLOCK () Try to acquire a sleep lock. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * bool_t SLEEP_TRYLOCK (sleep_t * lockp); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be acquired. * *-DESCRIPTION: * If the lock specified by "lockp" is immediately available (can be * acquired without sleeping) SLEEP_TRYLOCK () acquires the lock. If the * lock is not immediately available, the function returns without * acquiring the lock. * *-RETURN VALUE: * SLEEP_TRYLOCK () returns TRUE (a non-zero value) if the lock is * successfully acquired or FALSE (zero) if the lock is not acquired. * *-LEVEL: * Base or interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), * SLEEP_LOCKAVAIL (), SLEEP_LOCKOWNED (), SLEEP_UNLOCK () */ #if __USE_PROTO__ bool_t (SLEEP_TRYLOCK) (sleep_t * lockp) #else bool_t SLEEP_TRYLOCK __ARGS ((lockp)) sleep_t * lockp; #endif { ASSERT (lockp != NULL); if (ATOMIC_TEST_AND_SET_UCHAR (lockp->sl_locked) == 0) { lockp->sl_holder = PROC_HANDLE (); return TRUE; } return FALSE; } /* *-STATUS: * DDI/DKI * *-NAME: * SLEEP_UNLOCK () Release a sleep lock. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void SLEEP_UNLOCK (sleep_t * lockp); * *-ARGUMENTS: * lockp Pointer to the sleep lock to be released. * *-DESCRIPTION: * SLEEP_UNLOCK () releases the sleep lock specified by "lockp". If there * are processes waiting for the lock, one of the waiting processes is * awakened. * *-RETURN VALUE: * None. * *-LEVEL: * Base or interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SLEEP_ALLOC (), SLEEP_DEALLOC (), SLEEP_LOCK (), SLEEP_LOCK_SIG (), * SLEEP_LOCKAVAIL (), SLEEP_LOCKOWNED (), SLEEP_TRYLOCK () */ #if __USE_PROTO__ void (SLEEP_UNLOCK) (sleep_t * lockp) #else void SLEEP_UNLOCK __ARGS ((lockp)) sleep_t * lockp; #endif { pl_t prev_pl; /* * Make some assertions. Note that we *don't* assert that our * PROC_HANDLE () matches "sl_holder", since thanks to * SLEEP_TRYLOCK () allowing interrupt contexts to acquire sleep * locks we can't rely on that. */ ASSERT (lockp != NULL); ASSERT (ATOMIC_FETCH_UCHAR (lockp->sl_locked)); /* * When we are unlocking the sleep lock, we must lock the process * list so we can test whether there are any waiting processes to * be given the lock. */ prev_pl = PLIST_LOCK (lockp->sl_plist, "SLEEP_UNLOCK"); /* * If there are no waiters, we unlock the sleep lock, otherwise we * wake the first waiting process and leave the lock in the locked * state for the process to simply take over from us later. * * In order to cross-check things with the sleep function, we write * the expected processes' identity into the lock. */ if ((lockp->sl_holder = WAKE_ONE (lockp->sl_plist)) == NULL) { /* * No waiting processes, give the lock away. */ ATOMIC_CLEAR_UCHAR (lockp->sl_locked); } PLIST_UNLOCK (lockp->sl_plist, prev_pl); } /* *-STATUS: * DDI/DKI * *-NAME: * SV_ALLOC () Allocate and initialize a synchronization variable. * *-SYNOPSIS: * #include <sys/kmem.h> * #include <sys/ksynch.h> * * sv_t * SV_ALLOC (int flag); * *-ARGUMENTS: * flag Specifies whether the caller is willing to sleep * waiting for memory. If "flag" is set to KM_SLEEP, the * caller will sleep if necessary until sufficient memory * is available. If "flag" is set to KM_NOSLEEP, the * caller will not sleep, but SLEEP_ALLOC () will return * NULL if sufficient memory is not immediately * available. * *-DESCRIPTION: * SV_ALLOC () dynamically allocates and initialises an instance of a * synchronization variable. * *-RETURN VALUE: * Upon successful completion, SV_ALLOC () returns a pointer to the newly * allocated synchronization variable. If KM_NOSLEEP is specified and * sufficient memory is not immediately available, SV_ALLOC () returns a * NULL pointer. * *-LEVEL: * Base only if "flag" is set to KM_SLEEP. Base or Interrupt if "flag" is * set to KM_NOSLEEP. * *-NOTES: * May sleep if "flag" is set to KM_NOSLEEP. * * Driver-defined basic locks and read/write locks may be held across * calls to this function if "flag" is set to KM_NOSLEEP but may not be * held if "flag" is KM_SLEEP. * * Driver-defined sleep locks may be held across calls to this function * regardless of the value of "flag". * *-SEE_ALSO: * SV_BROADCAST (), SV_DEALLOC (), SV_SIGNAL (), SV_WAIT (), * SV_WAIT_SIG () */ #if __USE_PROTO__ sv_t * (SV_ALLOC) (int flag) #else sv_t * SV_ALLOC __ARGS ((flag)) int flag; #endif { sv_t * svp; ASSERT (flag == KM_SLEEP || flag == KM_NOSLEEP); /* * Allocate and initialise the data, possibly waiting for enough * memory to become available. */ if ((svp = (sv_t *) _lock_malloc (sizeof (* svp), flag)) != NULL) { INIT_LNODE (& svp->sv_node, NULL, & synch_vars, flag); PLIST_INIT (svp->sv_plist); } return svp; } /* *-STATUS: * DDI/DKI * *-NAME: * SV_BROADCAST () Wake up all processes sleeping on a synchronization * variable. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void * SV_BROADCAST (sv_t * svp, int flags); * *-ARGUMENTS: * svp Pointer to the synchronization variable to be * broadcast signalled. * * flags Bit field for flags. No flags are defined for use in * drivers and the "flags" argument must be set to zero. * *-DESCRIPTION: * If one or more processes are blocked on the synchronization variable * specified by "svp", SV_BROADCAST () wakes up all of the blocked * processes. Note that synchronization variables are stateless, and * therefore calls to SV_BROADCAST () only affect processes currently * blocked on the synchronization variable and have not effect on * processes that block on the synchronization variable at a later time. * *-RETURN VALUE: * None. * *-LEVEL: * Base or interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks and read/write locks may be held across * calls to this function if "flag" is set to KM_NOSLEEP but may not be * held if "flag" is KM_SLEEP. * * Driver-defined basic locks, read/write locks and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SV_ALLOC (), SV_DEALLOC (), SV_SIGNAL (), SV_WAIT (), SV_WAIT_SIG () */ #if __USE_PROTO__ void (SV_BROADCAST) (sv_t * svp, int flags) #else void SV_BROADCAST __ARGS ((svp, flags)) sv_t * svp; int flags; #endif { pl_t prev_pl; ASSERT (flags == 0); ASSERT (svp != NULL); /* * Lock the list (required by WAKE_ALL ()), wake the sleepers, and * unlock the list. */ prev_pl = PLIST_LOCK (svp->sv_plist, "SV_BROADCAST"); WAKE_ALL (svp->sv_plist); PLIST_UNLOCK (svp->sv_plist, prev_pl); } /* *-STATUS: * DDI/DKI * *-NAME: * SV_DEALLOC () Deallocate an instance of a synchronization variable. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void SV_DEALLOC (sv_t * svp); * *-ARGUMENTS: * svp Pointer to the synchronization variable to be * deallocated. * *-DESCRIPTION: * SV_DEALLOC () deallocates the synchronization variable specified by * "svp". * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SV_ALLOC (), SV_BROADCAST (), SV_SIGNAL (), SV_WAIT (), SV_WAIT_SIG () */ #if __USE_PROTO__ void (SV_DEALLOC) (sv_t * svp) #else void SV_DEALLOC __ARGS ((svp)) sv_t * svp; #endif { ASSERT (svp != NULL); /* * Remove from the list of all synchronization variables and free any * statistics buffer space before freeing the lock itself. */ PLIST_DESTROY (svp->sv_plist); FREE_LNODE (& svp->sv_node, & synch_vars); _lock_free (svp, sizeof (* svp)); } /* *-STATUS: * DDI/DKI * *-NAME: * SV_SIGNAL () Wake up one process sleeping on a synchronization * variable. * *-SYNOPSIS: * #include <sys/ksynch.h> * * void SV_SIGNAL (sv_t * svp, int flags); * *-ARGUMENTS: * svp Pointer to the synchronization variable to be * signalled. * * flags Bit field for flags. No flags are defined for use in * drivers and the "flags" argument must be set to zero. * *-DESCRIPTION: * If one or more processes are blocked on the synchronization variable * specified by "svp", SV_SIGNAL () wakes up a single blocked process. * Note that synchronization variables are stateless, and therefore * calls to SV_SIGNAL only affect processes currently blocked on the * synchronization variable and have no effect on processes that block on * the synchronization variable at a later time. * *-RETURN VALUE: * None. * *-LEVEL: * Base or Interrupt. * *-NOTES: * Does not sleep. * * Driver-defined basic locks, read/write locks, and sleep locks may be * held across calls to this function. * *-SEE_ALSO: * SV_ALLOC (), SV_BROADCAST (), SV_DEALLOC (), SV_WAIT (), * SV_WAIT_SIG () */ #if __USE_PROTO__ void (SV_SIGNAL) (sv_t * svp, int flags) #else void SV_SIGNAL __ARGS ((svp, flags)) sv_t * svp; int flags; #endif { pl_t prev_pl; ASSERT (flags == 0); ASSERT (svp != NULL); /* * Lock the list (required by WAKE_ONE ()), wake a sleeper, and * unlock the list. */ prev_pl = PLIST_LOCK (svp->sv_plist, "SV_SIGNAL"); (void) WAKE_ONE (svp->sv_plist); PLIST_UNLOCK (svp->sv_plist, prev_pl); } /* *-STATUS: * DDI/DKI * *-NAME: * SV_WAIT () Sleep on a synchronization variable. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * void SV_WAIT (sv_t * svp, int priority, lock_t * lkp); * *-ARGUMENTS: * svp Pointer to the synchronization variable on which to * sleep. * * priority A hint to the scheduling policy as to the relative * priority the caller wishes to be assigned while * running in the kernel after waking up. The valid * values for this argument are as follows: * * pridisk Priority appropriate for disk driver * prinet Priority appropriate for network driver. * pritty Priority appropriate for terminal driver. * pritape Priority appropriate for tape driver. * prihi High priority. * primed Medium priority. * prilo Low priority. * * Drivers may use these values to request a priority * appropriate to a given type of device or to request a * priority that is high, medium or low relative to other * activities within the kernel. * * It is also permissible to specify positive or negative * offsets from the values defined above. Positive * offsets result in favourable priority. The maximum * allowable offset in all cases is 3 (eg. pridisk+3 * and pridisk-3 are valid values by pridisk+4 and * pridisk-4 are not valid). Offsets can be useful in * defining the relative importance of different locks or * resources that may be hald by a given driver. In * general, a higher relative priority should be used * when the caller is attempting to acquire a highly- * contended lock or resource, or when the caller is * already holding one or more locks or kernel resources * upon entry to SV_WAIT (). * * The exact semantics of the "priority" argument is * specific to the scheduling class of the caller, and * some scheduling classes may choose to ignore the * argument for the purposes of assigning a scheduling * priority. * * lkp Pointer to a basic lock which must be locked when * SV_WAIT () is called. The basic lock is released when * the calling process goes to sleep, as described below. *-DESCRIPTION: * SV_WAIT () causes the calling process to go to sleep (the caller's * execution is suspended and other processes may be scheduled) waiting * for a call to SV_SIGNAL () or SV_BROADCAST () for the synchronization * variable specified by "svp". * * The basic lock specified by "lkp" must be held by the caller upon * entry. The lock is released and the interrupt priority level is set to * plbase after the process is queued on the synchronization variable but * prior to switching context switching to another process. When the * caller returns from SV_WAIT () the basic lock is not held and the * interrupt priority level is equal to plbase. * * The caller will not be interrupted by signals while sleeping inside * SV_WAIT (). * *-RETURN VALUE: * None. * *-LEVEL: * Base level only. * *-NOTES: * May sleep. * * Driver-defined basic locks and read/write locks may not be held across * calls to this function. * * Driver-defined sleep locks may be held across calls to this function. * *-SEE_ALSO: * SV_ALLOC (), SV_BROADCAST (), SV_DEALLOC (), SV_SIGNAL (), * SV_WAIT_SIG () */ #if __USE_PROTO__ void (SV_WAIT) (sv_t * svp, int priority, lock_t * lkp) #else void SV_WAIT __ARGS ((svp, priority, lkp)) sv_t * svp; int priority; lock_t * lkp; #endif { ASSERT (svp != NULL); ASSERT (lkp != NULL); ASSERT_BASE_LEVEL (); /* * First off, we have to lock the process list. After this is done we * can safely release the client's basic lock, since once we have our * lock the rest of the wait operation will proceed atomically. */ (void) PLIST_LOCK (svp->sv_plist, "SV_WAIT"); UNLOCK (lkp, plhi); (void) MAKE_SLEEPING (svp->sv_plist, priority, SLEEP_NO_SIGNALS); (void) splbase (); /* let's make sure... */ } /* *-STATUS: * DDI/DKI * *-NAME: * SV_WAIT_SIG () Sleep on a synchronization variable. * *-SYNOPSIS: * #include <sys/types.h> * #include <sys/ksynch.h> * * bool_t SV_WAIT_SIG (sv_t * svp, int priority, lock_t * lkp); * *-ARGUMENTS: * svp Pointer to the synchronization variable on which to * sleep. * * priority A hint to the scheduling policy as to the relative * priority the caller wishes to be assigned while * running in the kernel after waking up. The valid * values for this argument are as follows: * * pridisk Priority appropriate for disk driver * prinet Priority appropriate for network driver. * pritty Priority appropriate for terminal driver. * pritape Priority appropriate for tape driver. * prihi High priority. * primed Medium priority. * prilo Low priority. * * Drivers may use these values to request a priority * appropriate to a given type of device or to request a * priority that is high, medium or low relative to other * activities within the kernel. * * It is also permissible to specify positive or negative * offsets from the values defined above. Positive * offsets result in favourable priority. The maximum * allowable offset in all cases is 3 (eg. pridisk+3 * and pridisk-3 are valid values by pridisk+4 and * pridisk-4 are not valid). Offsets can be useful in * defining the relative importance of different locks or * resources that may be hald by a given driver. In * general, a higher relative priority should be used * when the caller is attempting to acquire a highly- * contended lock or resource, or when the caller is * already holding one or more locks or kernel resources * upon entry to SV_WAIT_SIG (). * * The exact semantics of the "priority" argument is * specific to the scheduling class of the caller, and * some scheduling classes may choose to ignore the * argument for the purposes of assigning a scheduling * priority. * * lkp Pointer to a basic lock which must be locked when * SV_WAIT_SIG () is called. The basic lock is released * when the calling process goes to sleep, as described * below. * *-DESCRIPTION: * SV_WAIT_SIG () causes the calling process to go to sleep (the caller's * execution is suspended and other processes may be scheduled) waiting * for a call to SV_SIGNAL () or SV_BROADCAST () for the synchronization * variable specified by "svp". * * The basic lock specified by "lkp" must be held by the caller upon * entry. The lock is released and the interrupt priority level is set to * plbase after the process is queued on the synchronization variable but * prior to switching context switching to another process. When the * caller returns from SV_WAIT_SIG () the basic lock is not held and the * interrupt priority level is equal to plbase. * * SV_WAIT_SIG () may be interrupted by a signal, in which case it will * return early without waiting for a call to SV_SIGNAL () or * SV_BROADCAST (). * * If the function is interrupted by a job control signal (eg SIGSTOP, * SIGTSTP, SIGTTIN, SIGTTOU) which results in the caller entering a * stopped state, when continued the SV_WAIT_SIG () function will return * TRUE as if the process had been awakened by a call to SV_SIGNAL () or * SV_BROADCAST (). * * If the caller is interrupted by a signal other than a job control * signal, or by a job control signal that does not result in the caller * stopping (because the signal has a non-default disposition), the * SV_WAIT_SIG () call will return FALSE. * *-RETURN VALUE: * SV_WAIT_SIG () returns TRUE (a non-zero value) if the caller woke up * because of a call to SV_SIGNAL () or SV_BROADCAST (), or if the caller * was stopped and subsequently continued. SV_WAIT_SIG () returns FALSE * (zero) if the caller woke up and returned early because of a signal * other than a job control stop signal, or by a job control signal that * did not result in the caller stopping because the signal has a non- * default disposition. * *-LEVEL: * Base level only. * *-NOTES: * May sleep. * * Driver-defined basic locks and read/write locks may not be held across * calls to this function. * * Driver-defined sleep locks may be held across calls to this function. * *-SEE_ALSO: * SV_ALLOC (), SV_BROADCAST (), SV_DEALLOC (), SV_SIGNAL (), SV_WAIT () */ #if __USE_PROTO__ bool_t (SV_WAIT_SIG) (sv_t * svp, int priority, lock_t * lkp) #else bool_t SV_WAIT_SIG __ARGS ((svp, priority, lkp)) sv_t * svp; int priority; lock_t * lkp; #endif { bool_t not_signalled; ASSERT (svp != NULL); ASSERT (lkp != NULL); ASSERT_BASE_LEVEL (); /* * First off, we have to lock the process list. After this is done we * can safely release the client's basic lock, since once we have our * lock the rest of the wait operation will proceed atomically. */ (void) PLIST_LOCK (svp->sv_plist, "SV_WAIT_SIG"); UNLOCK (lkp, plhi); not_signalled = MAKE_SLEEPING (svp->sv_plist, priority, SLEEP_INTERRUPTIBLE) != PROCESS_SIGNALLED; (void) splbase (); /* let's make sure */ return not_signalled; } /* * This function exercises some of the most basic facilities of the locking * system. It is difficult to assure that the code presented in this file * will work correctly, because the nature of the service provided is a * temporal guarantee that can't be verified without performing arbitrary * fine-grained interleavings of at least two execution paths through each * combination of interacting functions. * * So, the best we can do here in a portable fashion is to present some tests * of the observable properties of the above code, and to (hopefully) exercise * the ASSERT () statements in the above for some minimal self-checking. * * One problem with the use of ASSERT () tests is that it's difficult to * build negative tests. The argument to this function indicates a negative * tests that should be performed, presumably in conjunction with some test * script capable of verifying an exception report against a previous run. * * Our tests use a lock interrupt level appropriate to the environment; in * user-mode tests under Coherent, disabling interrupts is prohibited */ #if __COHERENT__ # define test_pl plbase #else # define test_pl plhi #endif #if __USE_PROTO__ int (LOCK_TESTS) (int negative) #else int LOCK_TESTS __ARGS ((negative)) int negative; #endif { pl_t prev_pl; lock_t * basic_lock; rwlock_t * rw_lock; sleep_t * sleep_lock; sv_t * synch_var; static lkinfo_t basic_info = { "test basic lock" }; static lkinfo_t rw_info = { "test read/write lock" }; static lkinfo_t sleep_info = { "test sleep lock" }; /* * Allocate some locks that we can play with... */ basic_lock = LOCK_ALLOC (32, test_pl, & basic_info, KM_SLEEP); rw_lock = RW_ALLOC (10, test_pl, & rw_info, KM_SLEEP); sleep_lock = SLEEP_ALLOC (0, & sleep_info, KM_SLEEP); synch_var = SV_ALLOC (KM_SLEEP); /* * Basic locks. */ if ((prev_pl = TRYLOCK (basic_lock, test_pl)) == invpl || TRYLOCK (basic_lock, test_pl) != invpl) return -1; UNLOCK (basic_lock, prev_pl); prev_pl = LOCK (basic_lock, test_pl); if (TRYLOCK (basic_lock, test_pl) != invpl) return -1; UNLOCK (basic_lock, prev_pl); /* * Read/write locks. */ if ((prev_pl = RW_TRYRDLOCK (rw_lock, test_pl)) == invpl || RW_TRYRDLOCK (rw_lock, test_pl) == invpl || RW_TRYWRLOCK (rw_lock, test_pl) != invpl) return -1; (void) RW_RDLOCK (rw_lock, test_pl); if (RW_TRYWRLOCK (rw_lock, test_pl) != invpl) return -1; RW_UNLOCK (rw_lock, prev_pl); RW_UNLOCK (rw_lock, prev_pl); RW_UNLOCK (rw_lock, prev_pl); if ((prev_pl = RW_TRYWRLOCK (rw_lock, test_pl)) == invpl || RW_TRYWRLOCK (rw_lock, test_pl) != invpl || RW_TRYRDLOCK (rw_lock, test_pl) != invpl) return -1; RW_UNLOCK (rw_lock, prev_pl); prev_pl = RW_WRLOCK (rw_lock, test_pl); if (RW_TRYWRLOCK (rw_lock, test_pl) != invpl || RW_TRYRDLOCK (rw_lock, test_pl) != invpl) return -1; RW_UNLOCK (rw_lock, prev_pl); /* * Sleep locks. */ if (SLEEP_LOCKOWNED (sleep_lock) == TRUE || SLEEP_LOCKAVAIL (sleep_lock) == FALSE || SLEEP_TRYLOCK (sleep_lock) == FALSE || SLEEP_LOCKOWNED (sleep_lock) == FALSE || SLEEP_LOCKAVAIL (sleep_lock) == TRUE || SLEEP_TRYLOCK (sleep_lock) == TRUE) return -1; SLEEP_UNLOCK (sleep_lock); if (SLEEP_LOCKOWNED (sleep_lock) == TRUE || SLEEP_LOCKAVAIL (sleep_lock) == FALSE || SLEEP_LOCK_SIG (sleep_lock, prilo) == FALSE || SLEEP_LOCKOWNED (sleep_lock) == FALSE || SLEEP_LOCKAVAIL (sleep_lock) == TRUE || SLEEP_TRYLOCK (sleep_lock) == TRUE) return -1; SLEEP_UNLOCK (sleep_lock); SLEEP_LOCK (sleep_lock, prilo); if (SLEEP_LOCKOWNED (sleep_lock) == FALSE || SLEEP_LOCKAVAIL (sleep_lock) == TRUE || SLEEP_TRYLOCK (sleep_lock) == TRUE) return -1; SLEEP_UNLOCK (sleep_lock); /* * Synchronisation variables: this is impossible to test without * access to timeout functions. Once DDI/DKI timeout functions are * available, we can use those to at least begin to exercise the * notion of synchronization. */ /* * Negative testing... need to add this! */ /* * Clean up and bail out. */ LOCK_DEALLOC (basic_lock); RW_DEALLOC (rw_lock); SLEEP_DEALLOC (sleep_lock); SV_DEALLOC (synch_var); return 0; }