/************************************************************************** * * * Copyright (C) 1994-1996 Silicon Graphics, Inc. * * * * These coded instructions, statements, and computer programs contain * * unpublished proprietary information of Silicon Graphics, Inc., and * * are protected by Federal copyright law. They may not be disclosed * * to third parties or copied or duplicated in any form, in whole or * * in part, without the prior written consent of Silicon Graphics, Inc. * * * **************************************************************************/ #ident "$Id: tkc.c,v 1.43 1997/09/05 15:55:04 mostek Exp $" #include "tk_private.h" /* * Each token controlled object has a set of up to 10 tokens associated with it. * The state for entire set of tokens is maintined in the tkci_cstate_t * data structure. This structure is allocated by the client using * the TKC_DECL macro. * Any operation may act on any or all of the tokens for a particular object - * the interface deals exclusivly with tk_set_t's. */ static void tkci_obtainwake(tkci_cstate_t *, int); static void tkci_obtainwait(tkci_cstate_t *, int, int *); #if TK_DEBUG static int tkci_nobtainwaiting(tkci_cstate_t *, int); #endif static void tkci_scwake(tkci_cstate_t *, int); static void tkci_scwait(tkci_cstate_t *, int, int *); static void tkci_badstate(char *, tkci_cstate_t *, int); static void __tkc_obtaining(tkc_state_t *, tk_set_t, tk_set_t *, tk_set_t *, tk_disp_t *, tk_set_t *, tk_set_t *, int, void *); static void __tkc_obtained(tkc_state_t *, tk_set_t, tk_set_t, tk_disp_t, int, void *); /* * table lookup for extlev downgrading. Basically WRITE and anything else * goes back to WRITE */ static unsigned char downext[] = { 0, /* No Token */ 1, /* READ -> READ */ 2, /* WRITE -> WRITE */ 2, /* READ|WRITE -> WRITE */ 4, /* SWRITE -> SWRITE */ 5, /* SWRITE|READ -> ILLEGAL */ 2, /* SWRITE|WRITE -> WRITE */ 7 /* READ|WRITE|SWRITE -> ILLEGAL */ }; /* * table lookup for which obtain bit to use. This defines the priority of * tokens if multiple obtains are outstanding. If we want WRITE then we get * that in preference, then SWRITE then READ. */ static unsigned char obtaintab[] = { 0, 1, /* READ -> READ */ 2, /* WRITE -> WRITE */ 2, /* READ|WRITE -> WRITE */ 4, /* SWRITE -> SWRITE */ 4, /* SWRITE|READ -> SWRITE */ 2, /* SWRITE|WRITE -> WRITE */ 2 /* SWRITE|WRITE|READ -> WRITE */ }; /* * macro to change states. Automatically calls scwake if the we should * call it when transitioning out of a state (based on waketab) */ #define NEWSTATE(ci,class,tstate,ns) \ { int wk = waketab[(tstate)->cl_state]; \ (tstate)->cl_state = ns; \ if (wk && (tstate)->cl_scwait) tkci_scwake(ci, class); \ } static unsigned char waketab[] = { 0, /* 0 */ 0, /* IDLE */ 0, /* OWNED */ 0, /* CACHED */ 1, /* REVOKING */ 0, /* OBTAINING */ 0, /* ALREADY */ 1, /* WILLRETURN */ 1, /* RETURNING */ 1, /* WILLOBTAIN */ 0, /* BORROWED */ 0, /* BORROWING */ 0, /* REJECTED */ 1, /* BREVOKING */ 1, /* SREVOKING */ 0, /* SOBTAINING */ 0, /* COBTAINING */ 0, /* BOBTAINING */ 1, /* SWILLOBTAIN */ 1, /* CREVOKING */ 0, /* CRESTORE */ 0, /* SRESTORE */ 0 /* BRESTORE */ }; static void tkc_local_obtain(void *, tk_set_t, tk_set_t, tk_disp_t, tk_set_t *); static void tkc_local_return(tkc_state_t, void *, tk_set_t, tk_set_t, tk_disp_t); tkc_ifstate_t tkc_local_iface = { tkc_local_obtain, tkc_local_return }; /* * testing/asserting state conditions */ #ifdef TK_DEBUG #define CHECK(t) tkci_checkstate(t) static void tkci_checkstate(tkci_clstate_t *t) { switch(t->cl_state) { case TK_CL_IDLE: /* * IDLE - extlev null; no hold count; * noone waiting to obtain; possible revoke request. */ TK_ASSERT(t->cl_extlev == 0); TK_ASSERT(t->cl_hold == 0); TK_ASSERT(t->cl_obtain == 0); break; case TK_CL_OWNED: case TK_CL_BORROWED: /* * OWNED - a valid extlev; a hold count; no revokes; * and the obtain mask shouldn't contain any bits that are * compatible with what we've already got */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ) && \ t->cl_extlev != 0); TK_ASSERT(t->cl_hold > 0); TK_ASSERT(t->cl_mrecall == 0); TK_ASSERT((t->cl_obtain & t->cl_extlev) == 0); break; case TK_CL_CACHED: /* * CACHED - a valid singleton extlev (only one bit set); * no hold count; noone waiting to obtain; * noone wanting to revoke */ TK_ASSERT((t->cl_extlev & t->cl_extlev - 1) == 0); TK_ASSERT(t->cl_hold == 0); TK_ASSERT(t->cl_mrecall == 0); TK_ASSERT(t->cl_obtain == 0); break; case TK_CL_OBTAINING: case TK_CL_SOBTAINING: case TK_CL_COBTAINING: case TK_CL_BOBTAINING: /* * OBTAINING - hold count > 0; valid extlev; potential revoke; * obtain mask may be anything * SOBTAINING - same. * COBTAINING - same. */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ) && \ t->cl_extlev != 0); TK_ASSERT(t->cl_hold > 0); break; case TK_CL_RETURNING: case TK_CL_CREVOKING: /* * RETURNING - hold count == 0; extlev singleton * CREVOKING - */ TK_ASSERT(t->cl_hold == 0); TK_ASSERT((t->cl_extlev & t->cl_extlev - 1) == 0); break; case TK_CL_WILLRETURN: /* * WILLRETURN - a valid extlev; a hold count; * and the obtain mask can be anything * Unlike OSF - we permit mandatory recalls - * to make sure that conditional recalls * always succed */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ) && \ t->cl_extlev != 0); TK_ASSERT(t->cl_hold > 0); break; case TK_CL_REVOKING: case TK_CL_SREVOKING: /* * REVOKING - a valid extlev; a hold count; no revokes; * and the obtain mask can be anything * SREVOKING - same except send a SERVER_MANDATORY msg */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ) && \ t->cl_extlev != 0); TK_ASSERT(t->cl_hold > 0); TK_ASSERT(t->cl_mrecall == 0); break; case TK_CL_BREVOKING: /* * BREVOKING - same as REVOKING except that * a) we should send a SERVER_CONDITIONAL msg * b) an have a 0 hold count since we can transition from * the CREVOKING state to here in tkci_creturn */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ) && \ t->cl_extlev != 0); TK_ASSERT(t->cl_mrecall == 0); break; case TK_CL_WILLOBTAIN: case TK_CL_SWILLOBTAIN: /* * WILLOBTAIN - a valid singleton extlev (only one bit set); * no hold count; */ TK_ASSERT(((t->cl_extlev & t->cl_extlev - 1) == 0) || \ t->cl_extlev == 0); TK_ASSERT(t->cl_hold == 0); break; case TK_CL_REJECTED: TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ)); TK_ASSERT(t->cl_hold > 0); TK_ASSERT((t->cl_obtain & t->cl_extlev) == 0); break; case TK_CL_BORROWING: /* * BORROWING - similar to OBTAINING: hold count > 0; * valid extlev; * obtain mask may be anything * XXX hard to say what the token paper means by * 'extended level may show any non-null set of * of compatible levels'. Does this mean it can be 0?? * For REJECTED we think so... */ TK_ASSERT(t->cl_extlev != (TK_SWRITE|TK_READ) && \ t->cl_extlev != (TK_SWRITE|TK_WRITE|TK_READ)); TK_ASSERT(t->cl_hold > 0); TK_ASSERT(t->cl_mrecall == 0); \ TK_ASSERT((t->cl_obtain & t->cl_extlev) == 0); break; default: __tk_fatal("unknown state t 0x%x state %s\n", t, __tkci_prstate(*t)); /* NOTREACHED */ } } #else /* TK_DEBUG */ #define CHECK(t) #endif /* obtain list manipulation */ static tk_list_t obtainhead; static tklock_t obtainlock; /* status change list */ static tk_list_t schead; static tklock_t sclock; /* * tkc_init - one time client side initialization */ void tkc_init(void) { __tk_lock_init(); obtainhead.tk_next = obtainhead.tk_prev = &obtainhead; __tk_create_lock(&obtainlock); schead.tk_next = schead.tk_prev = &schead; __tk_create_lock(&sclock); #if TK_TRC __tk_trace_init(); #endif #if TK_METER __tk_meter_init(); #endif #ifndef _KERNEL __tk_ts_init(); #endif } /* * tkc_local_obtain - get a token from a local token server */ static void tkc_local_obtain( void *tserver, tk_set_t to_be_obtained, tk_set_t to_be_returned, tk_disp_t whyreturn, tk_set_t *refused) { tk_set_t granted, already; /* * XXX since we don't have the client tkc pointer, we can't * really figure out in a generic way, which cell we're on. * This is easy with an underlying OS, so we simply don't * don't do this correctly for user level .. */ if (to_be_returned != TK_NULLSET) tks_return(tserver, TK_GETCH(NULL), to_be_returned, TK_NULLSET, TK_NULLSET, whyreturn); tks_obtain(tserver, TK_GETCH(NULL), to_be_obtained, &granted, refused, &already); TK_ASSERT(already == TK_NULLSET); } /* * tkc_local_return - return a token to a local token server * * For client initiated returns we must tell server first (emulating * an RPC). * For server initiated returns we must tell the client first since * once we tell the server, the entire token struct could be * torn down */ static void tkc_local_return( tkc_state_t tclient, void *tserver, tk_set_t tstoret, tk_set_t teh, tk_disp_t why) { tk_set_t clientrev; /* * compute the subset of tstoret that are to be * dispositioned as client initiated return */ clientrev = why & TK_DISP_CLIENT_ALL; clientrev |= (clientrev >> 1) | (clientrev >> 2); clientrev &= tstoret; if (clientrev != TK_NULLSET) { tks_return(tserver, TK_GETCH(NULL), clientrev, TK_NULLSET, TK_NULLSET, why); tkc_returned(tclient, clientrev, TK_NULLSET); /* all rest are server initiated */ TK_DIFF_SET(tstoret, clientrev); } if (tstoret != TK_NULLSET) tkc_returned(tclient, tstoret, TK_NULLSET); tks_return(tserver, TK_GETCH(NULL), tstoret, TK_NULLSET, teh, why); } /* * tkc_create_local - create a token managed object with local server */ void tkc_create_local( char *name, /* name for tracing/metering */ tkc_state_t *cs, /* object state to set up */ tks_state_t *ss, /* object server pointer */ int ntokens, /* # of tokens in set */ tk_set_t tsetcached, /* tokens to mark as present */ tk_set_t tset, /* tokens to mark as present and held */ void *tag) /* tag for tracing */ { tkc_create(name, cs, ss, &tkc_local_iface, ntokens, tsetcached, tset, tag); } /* * tkc_create - create a token managed object */ /* ARGSUSED */ void tkc_create( char *name, /* name for tracing/metering */ tkc_state_t *cs, /* object state to set up */ void *o, /* client object pointer */ tkc_ifstate_t *is, /* interface state */ int ntokens, /* # of tokens in set */ tk_set_t tsetcached, /* tokens to mark as present */ tk_set_t tset, /* tokens to mark as present and held */ void *tag) /* tag for tracing */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; TK_METER_DECL /* decls 'tm' */ tk_set_t ts, tsh; int i, class, level; tkci_clstate_t *tstate; TK_LOCKDECL; /* * try to catch races by setting ntokens to 0, and only when * everything is set up setting it to the 'real' number */ ci->tkci_ntokens = 0; ci->tkci_if = is; ci->tkci_obj = o; ci->tkci_flags = 0; ci->tkci_ref = 0; #if _KERNEL ci->tkci_origcell = cellid(); #endif for (i = 0; i < ntokens; i++) { ci->tkci_state[i].cl_word = 0; ci->tkci_state[i].cl_state = TK_CL_IDLE; } __tk_create_lock(&ci->tkci_lock); #if TK_METER tm = NULL; if (__tk_defaultmeter) tm = tkc_meteron(cs, name, tag); #endif /* eveything wanted to be held must be in first set */ ASSERT((tsetcached & tset) == tset); TK_LOCK(ci->tkci_lock); for (ts = tsetcached, tsh = tset, class = 0; ts; ts = ts >> TK_LWIDTH, tsh = tsh >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ntokens); TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; tstate->cl_extlev = level; if (tsh & TK_SET_MASK) { /* wants it held */ tstate->cl_state = TK_CL_OWNED; tstate->cl_hold = 1; } else { /* just cached please */ tstate->cl_state = TK_CL_CACHED; } } TK_UNLOCK(ci->tkci_lock); ci->tkci_ntokens = ntokens; TK_TRACE(TKC_CREATE, __return_address, TK_GETCH(ci->tkci_obj), tsetcached, tset, TK_NULLSET, ci, TK_METER_VAR); } /* * tkc_destroy - stop managing an object. * It is assumed that NO tokens are held and that NO requests for any * more tokens can come in. */ void tkc_destroy(tkc_state_t *cs) /* object to be destroyed */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; int i; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ TKC_METER_VAR_INIT(tm, cs); ASSERT(ci->tkci_if != &tkc_local_iface); TK_TRACE(TKC_DESTROY, __return_address, TK_GETCH(ci->tkci_obj), TK_NULLSET, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); for (i = 0; i < ci->tkci_ntokens; i++) { tstate = &ci->tkci_state[i]; if (tstate->cl_state != TK_CL_IDLE) __tk_fatal("tkc_destroy - not idle state cs 0x%x class %d state %s\n", cs, i, __tkci_prstate(*tstate)); /* NOTREACHED */ } #if TK_METER tkc_meteroff(cs); #endif ci->tkci_if = NULL; ci->tkci_obj = NULL; __tk_destroy_lock(&ci->tkci_lock); } /* * tkc_destroy_local - stop managing a local object. * It is assumed that NO tokens are held and that NO requests for any * more tokens can come in. * Cached tokens are returned to server. */ void tkc_destroy_local(tkc_state_t *cs) /* object to be destroyed */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; int class; tk_set_t toret = TK_NULLSET; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ TKC_METER_VAR_INIT(tm, cs); ASSERT(ci->tkci_if == &tkc_local_iface); TK_TRACE(TKC_DESTROY, __return_address, TK_GETCH(ci->tkci_obj), TK_NULLSET, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); for (class = 0; class < ci->tkci_ntokens; class++) { tstate = &ci->tkci_state[class]; ASSERT(tstate->cl_obtain == 0); if (tstate->cl_state == TK_CL_CACHED) { ASSERT(downext[tstate->cl_extlev]); TK_COMBINE_SET(toret, TK_MAKE(class, downext[tstate->cl_extlev])); } else if (tstate->cl_state != TK_CL_IDLE) { __tk_fatal("tkc_destroy_local - not idle/cached state cs 0x%x class %d state %s\n", cs, class, __tkci_prstate(*tstate)); /* NOTREACHED */ } } if (toret) tks_return(ci->tkci_obj, TK_GETCH(NULL), toret, TK_NULLSET, TK_NULLSET, TK_DISP_CLIENT_ALL); #if TK_METER tkc_meteroff(cs); #endif ci->tkci_if = NULL; ci->tkci_obj = NULL; __tk_destroy_lock(&ci->tkci_lock); } /* * tkc_acquire1 - a fast path acquire * Returns 0 if got token, -1 if token refused, errno on out of band error. */ int tkc_acquire1(tkc_state_t *cs, tk_singleton_t tsing) { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; auto tk_set_t gotten; TK_METER_DECL /* decls 'tm' */ int class = (tsing >> TK_LWIDTH) & 0xF; int level = tsing & TK_SET_MASK; TK_LOCKDECL; TK_ASSERT(tsing & 0x80000000);/* !passed in a tk_set_t */ TKC_METER_VAR_INIT(tm, cs); TKC_INC_METER(tm, class, acquire); TK_ASSERT((level & level - 1) == 0); TK_LOCK(ci->tkci_lock); tstate = &ci->tkci_state[class]; CHECK(tstate); if ((level & tstate->cl_extlev) || (tstate->cl_extlev == TK_WRITE)) { if ((tstate->cl_state == TK_CL_CACHED) || (tstate->cl_state == TK_CL_OWNED)) { /* * asking for the level we own OR * we have the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting token * XXX do we need to turn off obtain bit for the * OWNED case as in tkc_obtaining?? */ tstate->cl_hold++; tstate->cl_state = TK_CL_OWNED; tstate->cl_extlev |= level; TKC_INC_METER(tm, class, acq_cached); TK_TRACE(TKC_FACQUIRE, __return_address, TK_GETCH(ci->tkci_obj), TK_MAKE(class, level), TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); return 0; } } TK_UNLOCK(ci->tkci_lock); tkc_acquire(cs, TK_MAKE(class, level), &gotten); return gotten == TK_NULLSET ? 1 : 0; } void tkc_release1(tkc_state_t *cs, tk_singleton_t tsing) { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ int class; #if TK_DEBUG || TK_TRC int level; #endif TK_LOCKDECL; #if TK_DEBUG || TK_TRC level = tsing & TK_SET_MASK; #endif TK_ASSERT(tsing & 0x80000000);/* !passed in a tk_set_t */ TKC_METER_VAR_INIT(tm, cs); TK_ASSERT((level & level - 1) == 0); TK_LOCK(ci->tkci_lock); class = (tsing >> TK_LWIDTH) & 0xF; tstate = &ci->tkci_state[class]; CHECK(tstate); if (tstate->cl_state == TK_CL_OWNED && !tstate->cl_obtain) { TK_ASSERT((tsing & TK_SET_MASK) & tstate->cl_extlev); if (--tstate->cl_hold == 0) { /* turn off appropriate extlev bits. */ tstate->cl_extlev = downext[tstate->cl_extlev]; tstate->cl_state = TK_CL_CACHED; } TK_TRACE(TKC_FRELEASE, __return_address, TK_GETCH(ci->tkci_obj), TK_MAKE(class, level), TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); } else { TK_UNLOCK(ci->tkci_lock); tkc_release(cs, TK_MAKE(class, tsing & TK_SET_MASK)); } } /* * tkc_obtaining - get a token(s) * The tokens are obtained from the server if they are not present * on the client cell and held. While held, they cannot be recalled. * Note that ltoret is only updated when ltoobtain is so we don't * have to check it. */ void tkc_obtaining( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens requested */ tk_set_t *tstoobtain, /* tokens to obtain */ tk_set_t *tstoret, /* tokens to return */ tk_disp_t *dofret, /* disposition of tokens to return */ tk_set_t *tsrefused, /* tokens that were refused */ tk_set_t *tslater) /* get later please! */ { __tkc_obtaining(cs, tset, tstoobtain, tstoret, dofret, tsrefused, tslater, 1, (void *)__return_address); } /* ARGSUSED */ static void __tkc_obtaining( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens requested */ tk_set_t *tstoobtain, /* tokens to obtain */ tk_set_t *tstoret, /* tokens to return */ tk_disp_t *dofret, /* disposition of tokens to return */ tk_set_t *tsrefused, /* tokens that were refused */ tk_set_t *tslater, /* get later please! */ int dotrace, void *ra) { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ tk_set_t ts; tk_set_t lrefused, ltoobtain, ltoret; tk_disp_t ldofret; int level, class; int setobtain; TK_LOCKDECL; lrefused = TK_NULLSET; ltoobtain = TK_NULLSET; ltoret = TK_NULLSET; ldofret = TK_NULLSET; TKC_METER_VAR_INIT(tm, cs); TK_ASSERT((tset & 0x80000000) == 0);/* !passed in a tk_singleton_t */ TK_LOCK(ci->tkci_lock); if (dotrace) TK_TRACE(TKC_SOBTAINING, ra, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); lrefused = TK_NULLSET; for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TKC_INC_METER(tm, class, acquire); setobtain = 0; tryagain: TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; CHECK(tstate); switch (tstate->cl_state) { case TK_CL_IDLE: /* * we can get here with the setobtain bit on * if a return was refused - which will place us * in the cached state. Then, some other thread could * go ahead and return the token, finally the thread * sleeping below in RETURNING wakes up and lands here. */ setobtain = 0; tstate->cl_hold++; tstate->cl_extlev = level; tstate->cl_state = TK_CL_OBTAINING; /* update list of tokens to obtain/return */ TK_COMBINE_SET(ltoobtain, TK_MAKE(class, level)); break; case TK_CL_CACHED: /* * we already have the token - if at a compatible * level, bump hold count and return * * We can get here with the setobtain bit on in * rare instances - if we got into a state * where we held the WR token and wanted the * RD and SWR tokens, both RD & SWR would * be set in the obtain mask. We would then turn * off the SWR bit and let that get obtained. * Then we would turn off the RD bit and wake up the * thread that set the RD bit. Lets assume that it * doesn't run for a while. Meanwhile the thread that * got the SWR token could release it - that would * place the token into the CACHED state. */ if (ltoobtain != TK_NULLSET) goto done; setobtain = 0; if (level == tstate->cl_extlev || (tstate->cl_extlev == TK_WRITE)) { /* * asking for the level we own OR * we have the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting * token */ tstate->cl_hold++; tstate->cl_state = TK_CL_OWNED; tstate->cl_extlev |= level; TKC_INC_METER(tm, class, acq_cached); } else { int oextlev; /* wants a conflicting token */ tstate->cl_hold++; tstate->cl_state = TK_CL_OBTAINING; oextlev = tstate->cl_extlev; tstate->cl_extlev = level; TKC_INC_METER(tm, class, acq_conflict); /* * update list of tokens to obtain/return */ TK_COMBINE_SET(ltoret, TK_MAKE(class, downext[oextlev])); TK_COMBINE_SET(ldofret, TK_MAKE(class, TK_CLIENT_INITIATED)); TK_COMBINE_SET(ltoobtain, TK_MAKE(class, level)); } break; case TK_CL_OWNED: case TK_CL_BORROWED: if (ltoobtain != TK_NULLSET) goto done; /* * we already have the token held - if at a compatible * level, bump hold count and return */ if ((level & tstate->cl_extlev) || (tstate->cl_extlev == TK_WRITE)) { /* * asking for the level we own OR * got the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting * token */ tstate->cl_hold++; tstate->cl_extlev |= level; tstate->cl_obtain &= ~level; setobtain = 0; TKC_INC_METER(tm, class, acq_cached); } else { /* * wants a conflicting token * This is equivalent to the client * calling tkc_recall. */ TKC_INC_METER(tm, class, acq_conflict); if (tstate->cl_state == TK_CL_BORROWED) tstate->cl_state = TK_CL_BREVOKING; else tstate->cl_state = TK_CL_REVOKING; if ((tstate->cl_obtain & level) == 0) { setobtain = 1; tstate->cl_obtain |= level; } CHECK(tstate); tkci_scwait(ci, class, TK_LOCKVARADDR); /* something has changed - try again */ TK_LOCK(ci->tkci_lock); goto tryagain; } break; case TK_CL_BORROWING: case TK_CL_OBTAINING: case TK_CL_SOBTAINING: case TK_CL_COBTAINING: /* * Should we wait here or go ahead and accumulate * everything to do? if and when we support * class acquisition ordering it will be important * to wait here. It also seems to minimize * deadlock situations where we have a token * (because we have already held some) and now * are waiting for one */ if (ltoobtain != TK_NULLSET) goto done; if ((level & tstate->cl_extlev) || (tstate->cl_extlev == TK_WRITE)) { /* * asking for the level we own OR * got the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting * token */ tstate->cl_hold++; tstate->cl_extlev |= level; /* * turn off obtain bit if it is on - this * could be READ - we might have had * both WRITE and READ in the obtain mask * and went after the WRITE token, leaving * the READ set in obtain. Since WRITE * implies READ, we certainly don't need to * 'get' it */ tstate->cl_obtain &= ~level; setobtain = 0; /* wait for token to arrive */ tkci_obtainwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); /* * unless we got into the REJECTED state * we should have the token so we can progress * to the next one */ if (tstate->cl_state != TK_CL_REJECTED) { TK_ASSERT( tstate->cl_state == TK_CL_OWNED || tstate->cl_state == TK_CL_BORROWED || tstate->cl_state == TK_CL_WILLRETURN || tstate->cl_state == TK_CL_SREVOKING || tstate->cl_state == TK_CL_BREVOKING || tstate->cl_state == TK_CL_REVOKING); break; } /* * The obtain was rejected * undo our hold * Note that if the hold count goes to zero * we always need to wake up folks * To clear a rejection all waiters must * be finished and the hold count get to 0 */ TK_COMBINE_SET(lrefused, TK_MAKE(class, level)); if (--tstate->cl_hold > 0) break; if (tstate->cl_obtain) { int toobtain; /* turn off appropriate extlev bits. */ /* XXXcheck out */ tstate->cl_extlev = downext[tstate->cl_extlev]; toobtain = obtaintab[tstate->cl_obtain]; TK_ASSERT(toobtain); tstate->cl_obtain &= ~toobtain; tstate->cl_state = TK_CL_WILLOBTAIN; } else { tstate->cl_extlev = 0; tstate->cl_state = TK_CL_IDLE; TK_ASSERT(tkci_nobtainwaiting(ci, class) == 0); } if (tstate->cl_scwait) tkci_scwake(ci, class); } else { /* wants a conflicting token! */ if ((tstate->cl_obtain & level) == 0) { setobtain = 1; tstate->cl_obtain |= level; } TKC_INC_METER(tm, class, acq_conflict); tkci_scwait(ci, class, TK_LOCKVARADDR); /* something has changed - try again */ TK_LOCK(ci->tkci_lock); goto tryagain; } break; case TK_CL_WILLOBTAIN: case TK_CL_SWILLOBTAIN: { /* * This state permits us to proceed to get * a level - note that the obtain mask may * have been set to more than one level - the thread * that sets the WILLOBTAIN state will turn off only * one bit * Only the thread that turned on the obtain bit can * use this state. Consider: * An initial state: * we hold SWR * we want WRITE (this is set in the obtain mask * and the state set to REVOKING). * We then release SWR which starts a revoke sequence * and changes the state to RETURNING. * The return completes (having sent a one-way * message to the server), and the state changes to * WILLOBTAIN (and the WR bit is turned off in * obtain mask). * Another thread comes in wanting SWR - if this thread * gets the WILLOBTAIN state (rather than the thread * that set the WR bit), it will send an obtain message * to the server. This obtain message can overtake the * return message at the server which will confuse the * server into returning an 'already' indication * followed by the server removing the fact that * we own the token! * * We are only permitted in this state if we set * and obtain bit AND that bit has been turned off. */ int oextlev; if (ltoobtain != TK_NULLSET) goto done; if (setobtain == 0 || (tstate->cl_obtain & level)) { tkci_scwait(ci, class, TK_LOCKVARADDR); /* something has changed - try again */ TK_LOCK(ci->tkci_lock); goto tryagain; } else { tstate->cl_hold++; oextlev = tstate->cl_extlev; tstate->cl_extlev = level; setobtain = 0; /* * If we're in the SWILLOBTAIN state then * the server has sent a recall for the * token we currently have (and will be * returning). We must of course return * it with a TK_SERVER_MANDATORY message - * something a standard returned token * can't do. So we do a (*tkc_return) * callout */ if (tstate->cl_state == TK_CL_SWILLOBTAIN) { TK_ASSERT(oextlev != level); TK_COMBINE_SET(ltoret, TK_MAKE(class, oextlev)); TK_COMBINE_SET(ldofret, TK_MAKE(class, TK_SERVER_MANDATORY)); } else if (oextlev != level) { /* * we have to return a token if it * conflicts with the one we want */ TK_COMBINE_SET(ltoret, TK_MAKE(class, oextlev)); TK_COMBINE_SET(ldofret, TK_MAKE(class, TK_CLIENT_INITIATED)); } /* * This wakeup only for folks just above us * who went to sleep in WILLOBTAIN */ NEWSTATE(ci, class, tstate, TK_CL_OBTAINING); TK_COMBINE_SET(ltoobtain, TK_MAKE(class, level)); } break; } case TK_CL_REJECTED: case TK_CL_REVOKING: case TK_CL_BREVOKING: case TK_CL_SREVOKING: case TK_CL_CREVOKING: case TK_CL_WILLRETURN: case TK_CL_RETURNING: /* * In all these state we can't grant the token - * we must wait until the hold count goes to zero */ if (ltoobtain != TK_NULLSET) goto done; TKC_INC_METER(tm, class, acq_outbound); if ((tstate->cl_obtain & level) == 0) { setobtain = 1; tstate->cl_obtain |= level; } tkci_scwait(ci, class, TK_LOCKVARADDR); /* something has changed - try again */ TK_LOCK(ci->tkci_lock); goto tryagain; default: tkci_badstate("tkc_obtaining", ci, class); /* NOTREACHED */ } CHECK(tstate); TK_ASSERT(setobtain == 0); } done: if (dotrace) { TK_TRACE(TKC_EOBTAINING, 0, TK_GETCH(ci->tkci_obj), ltoobtain, ts << (TK_LWIDTH * class), lrefused, ci, TK_METER_VAR); TK_TRACE(TKC_EOBTAINING2, 0, TK_GETCH(ci->tkci_obj), ltoret, TK_NULLSET, ldofret, ci, TK_METER_VAR); } TK_UNLOCK(ci->tkci_lock); *tstoobtain = ltoobtain; *tstoret = ltoret; *tsrefused = lrefused; *tslater = ts << (TK_LWIDTH * class); *dofret = ldofret; } /* * called to initiate obtain/return requests */ void tkc_obtained( tkc_state_t *cs, tk_set_t tsobtained, tk_set_t tsrefused, tk_disp_t dofref) { __tkc_obtained(cs, tsobtained, tsrefused, dofref, 1, (void *)__return_address); } /* ARGSUSED */ static void __tkc_obtained( tkc_state_t *cs, tk_set_t tsobtained, tk_set_t tsrefused, tk_disp_t dofref, int dotrace, void *ra) { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; tk_set_t ts; int class, level, disp; TK_METER_DECL /* decls 'tm' */ TK_LOCKDECL; TKC_METER_VAR_INIT(tm, cs); TK_ASSERT((tsrefused & tsobtained) == 0); TK_LOCK(ci->tkci_lock); if (dotrace) TK_TRACE(TKC_SOBTAINED, ra, TK_GETCH(ci->tkci_obj), \ tsobtained, tsrefused, TK_NULLSET, ci, TK_METER_VAR); /* * Deal with successfully obtained tokens */ for (ts = tsobtained, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; CHECK(tstate); switch (tstate->cl_state) { case TK_CL_SOBTAINING: /* * we got the token but it must be returned as soon * as possible (server wants it) */ tstate->cl_state = TK_CL_SREVOKING; break; case TK_CL_COBTAINING: /* * we got the token but it must be returned as soon * as possible (client requested it) */ tstate->cl_state = TK_CL_REVOKING; break; case TK_CL_BOBTAINING: /* * we got the token but it must be returned as soon * as possible (server recalled it) */ tstate->cl_state = TK_CL_BREVOKING; break; case TK_CL_BORROWING: tstate->cl_obtain &= ~level; tstate->cl_state = TK_CL_BORROWED; break; case TK_CL_OBTAINING: tstate->cl_obtain &= ~level; tstate->cl_state = TK_CL_OWNED; break; default: tkci_badstate("tkc_obtained", ci, class); /* NOTREACHED */ } CHECK(tstate); /* XXX would be nice to only call wake if we knew * someone was waiting */ tkci_obtainwake(ci, class); } /* * Deal with refused tokens * In the case of (*tkc_obtain) refusing to return a token, * the refuse set can have multiple levels on. */ for (ts = tsrefused, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); tstate = &ci->tkci_state[class]; CHECK(tstate); /* * If we have a disposition for this token, then * we must have been trying to return it. */ disp = TK_GET_LEVEL(dofref, class); if (disp) { TK_ASSERT((disp & disp - 1) == 0); /* * since we were returning this, we shouldn't 'have' * it */ TK_ASSERT((level & downext[tstate->cl_extlev]) == 0); switch (tstate->cl_state) { case TK_CL_COBTAINING: case TK_CL_OBTAINING: if (--tstate->cl_hold == 0) { } else { /* put into restore state based * on disposition */ tstate->cl_extlev = level; if (disp == TK_CLIENT_INITIATED) { tstate->cl_state = TK_CL_CRESTORE; } else if (disp == TK_SERVER_MANDATORY) { tstate->cl_state = TK_CL_SRESTORE; } else { TK_ASSERT(disp == TK_SERVER_CONDITIONAL); tstate->cl_state = TK_CL_BRESTORE; } tkci_obtainwake(ci, class); } default: tkci_badstate("tkc_obtained3", ci, class); /* NOTREACHED */ } CHECK(tstate); continue; } /* * standard! refuse of obtain * Seems like we might be able to get into the COBTAINING * or BOBTAINING states since it's legal for the client * to call tkc_recall() on a token being obtained. * We shouldn't be able to get SOBTAINING - we don't * have the token so how can the server ask for it back? * XXX actually for now we can't get into BORROWING nor * BOBTAINING. */ TK_ASSERT((level & level - 1) == 0); switch (tstate->cl_state) { case TK_CL_COBTAINING: /* * client wanted to revoke token but we refused * to get it. Sounds like a standoff.. * Ignore revoke. */ /* FALLTHROUGH */ case TK_CL_BORROWING: case TK_CL_OBTAINING: if (--tstate->cl_hold == 0) { if (tstate->cl_obtain) { int toobtain; /* turn off appropriate extlev bits. */ /* XXXcheckout */ tstate->cl_extlev = downext[tstate->cl_extlev]; toobtain = obtaintab[tstate->cl_obtain]; TK_ASSERT(toobtain); tstate->cl_obtain &= ~toobtain; tstate->cl_state = TK_CL_WILLOBTAIN; if (tstate->cl_scwait) tkci_scwake(ci, class); } else { tstate->cl_extlev = 0; tstate->cl_state = TK_CL_IDLE; TK_ASSERT(tkci_nobtainwaiting(ci, class) == 0); } } else { /* * XX we clearly need to handle either * extlev or obtain mask here - in the * OBTAINING or RETURNING state we are * permitted to have the obtain mask * overlap the extlev, but not in the * REJECTED state */ tstate->cl_extlev = 0; tstate->cl_state = TK_CL_REJECTED; tkci_obtainwake(ci, class); } break; default: tkci_badstate("tkc_obtained2", ci, class); /* NOTREACHED */ } CHECK(tstate); } if (dotrace) TK_TRACE(TKC_EOBTAINED, 0, TK_GETCH(ci->tkci_obj), TK_NULLSET, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); } /* * tkc_acquire - conditionally acquire. */ void tkc_acquire( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens requested */ tk_set_t *got) /* tokens gotten */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; TK_METER_DECL /* decls 'tm' */ tk_set_t tstoret, tswanted, tstoobtain, obtained; tk_set_t tsrefused, tslater, gotten; tk_disp_t dofret; TKC_METER_VAR_INIT(tm, cs); TK_TRACE(TKC_SCACQUIRE, __return_address, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); tswanted = tset; gotten = TK_NULLSET; while (tswanted) { __tkc_obtaining(cs, tswanted, &tstoobtain, &tstoret, &dofret, &tsrefused, &tslater, 1, (void *)__return_address); gotten |= tswanted & ~(tstoobtain | tsrefused | tslater); if ((tstoobtain | tstoret) != 0) { /* * Refusal to return is really just for * error handling - if the server goes down * then (*tkc_obtain)() may well refuse all * returns and obtains. */ ci->tkci_if->tkc_obtain(ci->tkci_obj, tstoobtain, tstoret, dofret, &tsrefused); TK_ASSERT((tstoobtain & tsrefused) == tsrefused); #if TK_DEBUG /* * if any toreturn tokens were refused, then the obtain * token that prompted the return must also have * been refused */ if (tsrefused & tstoret) { tk_set_t ovlp, tsob; int class, oblevel; for ( ovlp = (tsrefused & tstoret), tsob = tstoobtain, class = 0; ovlp; ovlp = ovlp >> TK_LWIDTH, tsob = tsob >> TK_LWIDTH, class++) { if ((ovlp & TK_SET_MASK) == 0) continue; oblevel = tsob & TK_SET_MASK; /* must have wanted something */ TK_ASSERT(oblevel); /* what we wanted must also be refused */ TK_ASSERT(oblevel & TK_GET_LEVEL(tsrefused, class)); } } #endif obtained = tstoobtain & ~tsrefused; /* * it's fine to pass more disposition info * than is needed. */ __tkc_obtained(cs, obtained, tsrefused, dofret, 1, (void *)__return_address); gotten |= obtained; } tswanted = tslater; } TK_TRACE(TKC_ECACQUIRE, 0, TK_GETCH(ci->tkci_obj), gotten, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); *got = gotten; } /* * tkc_release - release a held token */ void tkc_release( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset) /* tokens to release */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ tk_set_t ts; tk_set_t tstoret = TK_NULLSET; tk_disp_t dofret = TK_NULLSET; /* REFERENCED (level) */ int level; int class; TK_LOCKDECL; TK_ASSERT((tset & 0x80000000) == 0);/* !passed in a tk_singleton_t */ TKC_METER_VAR_INIT(tm, cs); TK_LOCK(ci->tkci_lock); TK_TRACE(TKC_SRELEASE, __return_address, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; TK_ASSERT(tstate->cl_hold > 0); CHECK(tstate); switch (tstate->cl_state) { case TK_CL_BORROWED: case TK_CL_OWNED: TK_ASSERT(level & tstate->cl_extlev); if (--tstate->cl_hold == 0) { /* turn off appropriate extlev bits. */ tstate->cl_extlev = downext[tstate->cl_extlev]; if (tstate->cl_state == TK_CL_BORROWED) { tstate->cl_state = TK_CL_RETURNING; TK_COMBINE_SET(tstoret, TK_MAKE(class, tstate->cl_extlev)); TK_COMBINE_SET(dofret, TK_MAKE(class, TK_SERVER_CONDITIONAL)); if (tstate->cl_scwait) tkci_scwake(ci, class); } else if (tstate->cl_obtain) { int toobtain; toobtain = obtaintab[tstate->cl_obtain]; TK_ASSERT(toobtain); tstate->cl_obtain &= ~toobtain; tstate->cl_state = TK_CL_WILLOBTAIN; if (tstate->cl_scwait) tkci_scwake(ci, class); } else { tstate->cl_state = TK_CL_CACHED; } } break; case TK_CL_REVOKING: case TK_CL_SREVOKING: case TK_CL_BREVOKING: /* * token wants to be revoked - if hold count goes to * zero, call (*tkc_return)() callout. */ TK_ASSERT(level & tstate->cl_extlev); if (--tstate->cl_hold == 0) { /* turn off appropriate extlev bits. */ tstate->cl_extlev = downext[tstate->cl_extlev]; /* * XXX OSF seems to permit transition to * the WILLOBTAIN state if the obtain * mask is set. This seems to cause some * problems - extlev is still set so a thread * trying to get a conflicting token will * attempt to return it (in tkc_acquire) * as well as our sending it back * (via tkc_return) */ /* * the token level to revoke is the extlev * not what we're returning - we could * be returning a READ token but have * the WRITE or SWRITE token cached */ TK_COMBINE_SET(tstoret, TK_MAKE(class, tstate->cl_extlev)); if (tstate->cl_state == TK_CL_BREVOKING) { TK_COMBINE_SET(dofret, TK_MAKE(class, TK_SERVER_CONDITIONAL)); } else if (tstate->cl_state == TK_CL_SREVOKING) { TK_COMBINE_SET(dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); } else { TK_COMBINE_SET(dofret, TK_MAKE(class, TK_CLIENT_INITIATED)); } NEWSTATE(ci, class, tstate, TK_CL_RETURNING); } break; case TK_CL_WILLRETURN: /* * token wants to be returned - if hold count goes to * zero, wake returner * We go to a CREVOKING state rather than, like OSF * to the RETURNING state. We want RETURNING to mean * that the return message has gone out. Instead, * depending on which request kicks us out of * WILLRETURN we go into one of the 3 REVOKING states * This lets the thread waiting in tkc_returning * decide whether it needs to send a message. */ TK_ASSERT(level & tstate->cl_extlev); if (--tstate->cl_hold == 0) { /* turn off appropriate extlev bits. */ tstate->cl_extlev = downext[tstate->cl_extlev]; NEWSTATE(ci, class, tstate, TK_CL_CREVOKING); } break; default: tkci_badstate("tkc_release", ci, class); /* NOTREACHED */ } CHECK(tstate); } TK_TRACE(TKC_ERELEASE, 0, TK_GETCH(ci->tkci_obj), tstoret, TK_NULLSET, dofret, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); if (tstoret) { TK_TRACE(TKC_RETURN_CALL, __return_address, TK_GETCH(ci->tkci_obj), tstoret, TK_NULLSET, dofret, ci, TK_METER_VAR); ci->tkci_if->tkc_return(ci, ci->tkci_obj, tstoret, TK_NULLSET, dofret); } return; } /* * tkc_hold - hold a token. No attempt is made to acquire the token if * it is not on the client. The set of successfully held tokens is returned. */ tk_set_t tkc_hold( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset) /* tokens requested */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ tk_set_t ts, gts = TK_NULLSET; int level, class; TK_LOCKDECL; TK_ASSERT((tset & 0x80000000) == 0);/* !passed in a tk_singleton_t */ TKC_METER_VAR_INIT(tm, cs); TK_LOCK(ci->tkci_lock); TK_TRACE(TKC_SHOLD, __return_address, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { tryagain: if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; CHECK(tstate); TKC_INC_METER(tm, class, acquire); switch (tstate->cl_state) { case TK_CL_CACHED: /* * we already have the token - if at a compatible * level, bump hold count and return */ if (level == tstate->cl_extlev || (tstate->cl_extlev == TK_WRITE)) { /* * asking for the level we own OR * got the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting * token */ tstate->cl_hold++; tstate->cl_state = TK_CL_OWNED; tstate->cl_extlev |= level; TKC_INC_METER(tm, class, acq_cached); TK_COMBINE_SET(gts, TK_MAKE(class, level)); } break; case TK_CL_OWNED: case TK_CL_BORROWED: /* * we already have the token held - if at a compatible * level, bump hold count and return */ if ((level & tstate->cl_extlev) || (tstate->cl_extlev == TK_WRITE)) { /* * asking for the level we own OR * got the write token - its always * ok to get a token if we have the write token * level as no-one else can have a conflicting * token */ tstate->cl_hold++; tstate->cl_extlev |= level; TK_COMBINE_SET(gts, TK_MAKE(class, level)); TKC_INC_METER(tm, class, acq_cached); } break; case TK_CL_BORROWING: case TK_CL_OBTAINING: case TK_CL_COBTAINING: case TK_CL_BOBTAINING: if ((level & tstate->cl_extlev) || (tstate->cl_extlev == TK_WRITE)) { tkci_obtainwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); goto tryagain; } break; default: /* * these are all states for which we won't grant a * token */ #ifdef NEVER if (tstate->cl_state != TK_CL_IDLE) printf("tkc_hold - token 0x%x not granted because in state %s\n", cs, __tkci_prstate(*tstate)); #endif break; } CHECK(tstate); } TK_TRACE(TKC_EHOLD, 0, TK_GETCH(ci->tkci_obj), gts, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); return gts; } /* ARGSUSED */ static void tkci_mreturn( tkci_cstate_t *ci, /* object containing tokens */ tk_meter_t *tm, int class, int level, tk_set_t *tstoret, /* tokens to return */ tk_set_t *teh, tk_disp_t *dofret) { tkci_clstate_t *tstate; TKC_INC_METER(tm, class, recall); tstate = &ci->tkci_state[class]; CHECK(tstate); switch (tstate->cl_state) { case TK_CL_IDLE: /* * we can get here if the client spontaneously sent * back a token while the server was revoking it * but before we got the revoke message */ TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); break; case TK_CL_CACHED: TK_ASSERT((level & downext[tstate->cl_extlev])); tstate->cl_state = TK_CL_RETURNING; TK_COMBINE_SET(*tstoret, TK_MAKE(class, level)); TK_COMBINE_SET(*dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); break; case TK_CL_OWNED: TK_ASSERT((level & downext[tstate->cl_extlev])); tstate->cl_state = TK_CL_SREVOKING; break; case TK_CL_OBTAINING: /* * If we are in the obtaining state it is because the * client has voluntarily returned the token after * the revoke started, and then tried to reacquire * it. At this point the obtain will be stuck at the * server waiting for the response to this revoke * so we say that we don't know where the token is */ TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); break; case TK_CL_COBTAINING: /* * client wants to return it and now so does * the server - server wins */ TK_ASSERT(level & downext[tstate->cl_extlev]); tstate->cl_state = TK_CL_SOBTAINING; break; case TK_CL_RETURNING: TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); break; case TK_CL_REVOKING: /* * client wants to return token - so do we - no * need for 2 messages, change to server style * No need to use NEWSTATE - don't need to wake anyone */ tstate->cl_state = TK_CL_SREVOKING; break; case TK_CL_WILLRETURN: TK_ASSERT(level & downext[tstate->cl_extlev]); NEWSTATE(ci, class, tstate, TK_CL_SREVOKING); break; case TK_CL_WILLOBTAIN: /* * In this state, the thread that set this * state will ALWAYS be the one to return the current token, * There are a few possiblities: * 1) wait for status chg - but tkc_recall * isn't supposed to sleep * 2) return an 'eh' indication - this could * cause storms if the thread that owns the * WILLOBTAIN state doesn't run for a while * 3) somehow force the WILLOBTAIN thread to * place this token into the SREVOKING state. * 3) of course is the right answer. */ if (level & downext[tstate->cl_extlev]) tstate->cl_state = TK_CL_SWILLOBTAIN; else { TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofret, TK_MAKE(class, TK_SERVER_MANDATORY)); } break; case TK_CL_BREVOKING: case TK_CL_BOBTAINING: case TK_CL_BORROWED: case TK_CL_BORROWING: /* implies a conditional recall - but server can't both * conditionally and mandatorily ask use for token! */ default: tkci_badstate("tkci_mreturn", ci, class); /* NOTREACHED */ } CHECK(tstate); } /* * handle recalls initiated by client - in general these are just hints */ /* ARGSUSED */ static void tkci_recall_client( tkci_cstate_t *ci, /* object containing tokens */ tk_meter_t *tm, int class, int level, tk_set_t *tset, /* tokens to return */ tk_disp_t *dofset) { tkci_clstate_t *tstate; tstate = &ci->tkci_state[class]; CHECK(tstate); /* if we don't have it, ignore revoke 'request' */ if (!(level & downext[tstate->cl_extlev])) return; switch (tstate->cl_state) { case TK_CL_CACHED: TKC_INC_METER(tm, class, recall); tstate->cl_state = TK_CL_RETURNING; TK_COMBINE_SET(*tset, TK_MAKE(class, level)); TK_COMBINE_SET(*dofset, TK_MAKE(class, TK_CLIENT_INITIATED)); break; case TK_CL_BORROWED: TKC_INC_METER(tm, class, recall); tstate->cl_state = TK_CL_BREVOKING; break; case TK_CL_OWNED: TKC_INC_METER(tm, class, recall); tstate->cl_state = TK_CL_REVOKING; break; case TK_CL_OBTAINING: TKC_INC_METER(tm, class, recall); tstate->cl_state = TK_CL_COBTAINING; break; case TK_CL_BORROWING: TKC_INC_METER(tm, class, recall); tstate->cl_state = TK_CL_BOBTAINING; break; case TK_CL_SOBTAINING: case TK_CL_COBTAINING: case TK_CL_RETURNING: case TK_CL_REVOKING: case TK_CL_BREVOKING: case TK_CL_SREVOKING: case TK_CL_WILLRETURN: /* seems harmless enough */ break; case TK_CL_WILLOBTAIN: #ifdef NEVER /* * We may have already revoked and returned * this token, and just be permitting someone * else to obtain a token. * We don't set the mrecall bit - at this point there * is NO way that the server could believe we have * a token. */ break; #endif default: tkci_badstate("tkci_recall_client", ci, class); /* NOTREACHED */ } CHECK(tstate); } /* * tkci_creturn - called in response to a server initiated conditional * recall message */ /* ARGSUSED */ static void tkci_creturn( tkci_cstate_t *ci, /* object containing tokens */ tk_meter_t *tm, int class, int level, tk_set_t *tstoret, /* tokens to recall */ tk_set_t *teh, tk_disp_t *dofsets) { tkci_clstate_t *tstate; tstate = &ci->tkci_state[class]; CHECK(tstate); /* * For recall, level doesn't matter. If we don't think * we have it at all, call it unknown and add it to the 'eh' list. */ level = downext[tstate->cl_extlev]; if (level == 0) { TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofsets, TK_MAKE(class, TK_SERVER_CONDITIONAL)); return; } switch (tstate->cl_state) { case TK_CL_CACHED: tstate->cl_state = TK_CL_RETURNING; TK_COMBINE_SET(*tstoret, TK_MAKE(class, level)); TK_COMBINE_SET(*dofsets, TK_MAKE(class, TK_SERVER_CONDITIONAL)); break; case TK_CL_OWNED: tstate->cl_state = TK_CL_BORROWED; break; case TK_CL_OBTAINING: case TK_CL_RETURNING: /* * to avoid a deadlock we need to let this * recall complete w/o waiting for the client * to 'finish' the return. * If, in fact the server still thinks we have * the token, it'll send us another recall request */ TK_COMBINE_SET(*teh, TK_MAKE(class, level)); TK_COMBINE_SET(*dofsets, TK_MAKE(class, TK_SERVER_CONDITIONAL)); break; case TK_CL_WILLRETURN: /* * WILLRETURN is only entered from a client * initiated return via tkc_returning. * Since the server wants the token - its better * to let the return message be the one that * will satisfy the server rather than a * client initiated one. */ NEWSTATE(ci, class, tstate, TK_CL_BREVOKING); break; case TK_CL_REVOKING: /* no need to wake anyone thus no NEWSTATE */ tstate->cl_state = TK_CL_BREVOKING; break; case TK_CL_CREVOKING: /* * the hold count is zero and the thread that set * WILLRETURN in tkc_returning will wake and * realize that we've have already returned the * token. */ TK_COMBINE_SET(*tstoret, TK_MAKE(class, level)); TK_COMBINE_SET(*dofsets, TK_MAKE(class, TK_SERVER_CONDITIONAL)); NEWSTATE(ci, class, tstate, TK_CL_RETURNING); break; case TK_CL_BORROWED: case TK_CL_BORROWING: case TK_CL_BOBTAINING: case TK_CL_BREVOKING: /* this would imply 2 recalls! */ case TK_CL_SREVOKING: /* implies both a mandatory and conditional recall */ default: tkci_badstate("tkci_creturn", ci, class); /* NOTREACHED */ } CHECK(tstate); } /* * tkc_recall - called when: * 1) a client knows that its likely that another client would be acquiring * a token that would likely cause a recall. This can be a good * performance win (why == TK_CLIENT_INITIATED) * 2) in response to a mandatory server request (why == TK_SERVER_MANDATORY) * 3) in response to a conditional server request (TK_SERVER_CONDITIONAL) * * the 'why' argument encodes the reason per token. * * Since a CLIENT_INITIATED revoke is just a 'hint' if we don't think * we have the token, we just ignore it. * Server initiated recalls we MUST answer at some time - in fact, no * more acquires on the token will be permitted until we answer. */ void tkc_recall( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens to return */ tk_disp_t why) /* server or client initiated per token */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; TK_METER_DECL /* decls 'tm' */ tk_set_t ts; tk_set_t tstoret = TK_NULLSET, eh = TK_NULLSET; tk_disp_t ds, dofret = TK_NULLSET; int disp, level, class; TK_LOCKDECL; TKC_METER_VAR_INIT(tm, cs); TK_LOCK(ci->tkci_lock); TK_TRACE(TKC_SRECALL, __return_address, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, why, ci, TK_METER_VAR); for (ts = tset, ds = why, class = 0; ts; ts = ts >> TK_LWIDTH, ds = ds >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); disp = ds & TK_SET_MASK; TK_ASSERT(disp); TK_ASSERT((disp & disp - 1) == 0); if (disp == TK_SERVER_CONDITIONAL) tkci_creturn(ci, TK_METER_VAR, class, level, &tstoret, &eh, &dofret); else if (disp == TK_SERVER_MANDATORY) tkci_mreturn(ci, TK_METER_VAR, class, level, &tstoret, &eh, &dofret); else tkci_recall_client(ci, TK_METER_VAR, class, level, &tstoret, &dofret); } TK_TRACE(TKC_ERECALL, 0, TK_GETCH(ci->tkci_obj), tstoret, eh, dofret, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); if (tstoret || eh) { TK_TRACE(TKC_RETURN_CALL, __return_address, TK_GETCH(ci->tkci_obj), tstoret, eh, dofret, ci, TK_METER_VAR); ci->tkci_if->tkc_return(ci, ci->tkci_obj, tstoret, eh, dofret); } } /* * tkc_returned - return a set of tokens * Used by the client to disposition a set of tokens. It must call this * in these cases: * 1) to return tokens required by a call to (*tkc_return) * 2) to return tokens initiated via tkc_returning() * * Note that it is ILLEGAL to refuse to return a token that is server-initiated. */ void tkc_returned( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens to return */ tk_set_t refused) /* tokens refused */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ tk_set_t ts; /* REFERENCED (level) */ int level; int class; TK_LOCKDECL; TKC_METER_VAR_INIT(tm, cs); TK_LOCK(ci->tkci_lock); TK_TRACE(TKC_SRETURNED, __return_address, TK_GETCH(ci->tkci_obj), tset, refused, TK_NULLSET, ci, TK_METER_VAR); for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); TKC_INC_METER(tm, class, return); tstate = &ci->tkci_state[class]; CHECK(tstate); switch (tstate->cl_state) { case TK_CL_RETURNING: TK_ASSERT((tstate->cl_extlev & level) != 0); if (tstate->cl_obtain) { /* * There can be multiple bits on - including * the one for the level we're returning. This * can happen if say T1 has token for READ, * T2 wants it for WRITE (which set the * state to REVOKING and set WRITE in the * obtain mask); then T3 wants it for READ - * this will cause READ to get placed into * the obtain mask. */ int toobtain; toobtain = obtaintab[tstate->cl_obtain]; TK_ASSERT(toobtain); tstate->cl_obtain &= ~toobtain; tstate->cl_mrecall = 0; tstate->cl_extlev = 0; NEWSTATE(ci, class, tstate, TK_CL_WILLOBTAIN); } else { tstate->cl_mrecall = 0; tstate->cl_extlev = 0; NEWSTATE(ci, class, tstate, TK_CL_IDLE); } break; case TK_CL_OBTAINING: /* * another thread might have just returned a token while * obtaining a conflicting token * This can happen if the server recalled a token * that was in the WILLOBTAIN state (which * set the state to SWILLOBTAIN). When we change * the state to OBTAINING we send back the old * token (via a (*tkc_return)() callout). This * callout will send back the token and call us * * Note that cl_extlev could contain our token if * for example we are returning RD and another * thread is OBTAINING WR and a third thread goes * for RD - the extlev will show WR|RD, * which has nothing to do with our RD. */ TK_ASSERT((level & downext[tstate->cl_extlev]) == 0); break; default: tkci_badstate("tkc_returned", ci, class); /* NOTREACHED */ } CHECK(tstate); } for (ts = refused, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { if ((level = ts & TK_SET_MASK) == 0) continue; TK_ASSERT(class < ci->tkci_ntokens); TK_ASSERT((level & level - 1) == 0); tstate = &ci->tkci_state[class]; CHECK(tstate); switch (tstate->cl_state) { case TK_CL_RETURNING: TK_ASSERT((tstate->cl_extlev & level) != 0); if (tstate->cl_obtain) { /* * There can be multiple bits on - including * the one for the level we're returning. This * can happen if say T1 has token for READ, * T2 wants it for WRITE (which set the * state to REVOKING and set WRITE in the * obtain mask); then T3 wants it for READ - * this will cause READ to get placed into * the obtain mask. */ int toobtain; toobtain = obtaintab[tstate->cl_obtain]; TK_ASSERT(toobtain); tstate->cl_obtain &= ~toobtain; if (tstate->cl_extlev & toobtain) { /* what we've got and what they want * are the same - call it CACHED */ NEWSTATE(ci, class, tstate, TK_CL_CACHED); } else { NEWSTATE(ci, class, tstate, TK_CL_WILLOBTAIN); } } else { NEWSTATE(ci, class, tstate, TK_CL_CACHED); } break; case TK_CL_BORROWING: case TK_CL_OBTAINING: default: tkci_badstate("tkc_return-refused", ci, class); /* NOTREACHED */ } CHECK(tstate); } TK_TRACE(TKC_ERETURNED, 0, TK_GETCH(ci->tkci_obj), TK_NULLSET, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); } /* * tkc_returning - return a set of tokens * Used by the client to push tokens back to the server * We are particularly forgiving in what can be passed in tset - * it is very reasonable to turn on all 3 levels. We will return the * one we have .. * * If wait_to_resolve is TRUE then we may well sleep - since things can * change while we sleep, we basically must re-start the loop if we do. * So, we only return if we get through all of tset w/o sleeping * * Note that we use the WILLRETURN state - this basically means that no * other acquires may be done (they will PANIC) */ void tkc_returning( tkc_state_t *cs, /* object containing tokens */ tk_set_t tset, /* tokens to return */ tk_set_t *ok, /* OUT:tokens that are ok to return */ tk_disp_t *okwhy, /* OUT:disposition of ok tokens */ int wait_to_resolve) /* if TRUE then wait for tokens in process */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t *tstate; TK_METER_DECL /* decls 'tm' */ tk_set_t ts; int level, class; TK_LOCKDECL; TKC_METER_VAR_INIT(tm, cs); *ok = TK_NULLSET; *okwhy = TK_NULLSET; TK_LOCK(ci->tkci_lock); TK_TRACE(TKC_SRETURNING, __return_address, TK_GETCH(ci->tkci_obj), tset, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) { tryagain: if ((level = ts & TK_SET_MASK) == 0) continue; /* heck, they can even pass total garbage! */ if (class >= ci->tkci_ntokens) break; tstate = &ci->tkci_state[class]; CHECK(tstate); if ((downext[tstate->cl_extlev] & level) == 0) /* we don't have it */ continue; if (tstate->cl_state == TK_CL_CACHED) { tstate->cl_state = TK_CL_RETURNING; CHECK(tstate); TK_COMBINE_SET(*ok, TK_MAKE(class, downext[tstate->cl_extlev])); TK_COMBINE_SET(*okwhy, TK_MAKE(class, TK_CLIENT_INITIATED)); continue; } if (!wait_to_resolve) { /* thats all */ continue; } switch (tstate->cl_state) { case TK_CL_IDLE: /* ignore */ break; case TK_CL_SREVOKING: case TK_CL_BREVOKING: case TK_CL_RETURNING: tkci_scwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); goto tryagain; case TK_CL_BORROWED: tstate->cl_state = TK_CL_BREVOKING; tkci_scwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); goto tryagain; case TK_CL_OWNED: case TK_CL_REVOKING: /* * OWNED is changed to WILLRETURN to force no * more grants. * REVOKING is changed to WILLRETURN to avoid * an extra message - the caller has signified * their desire to send a message */ tstate->cl_state = TK_CL_WILLRETURN; CHECK(tstate); tkci_scwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); if (tstate->cl_state == TK_CL_CREVOKING) { TK_COMBINE_SET(*ok, TK_MAKE(class, downext[tstate->cl_extlev])); TK_COMBINE_SET(*okwhy, TK_MAKE(class, TK_CLIENT_INITIATED)); NEWSTATE(ci, class, tstate, TK_CL_RETURNING); break; } else { goto tryagain; } case TK_CL_COBTAINING: case TK_CL_SOBTAINING: case TK_CL_OBTAINING: case TK_CL_BORROWING: /* * XXX does this really get woken all the time even * though we don't set a hold count?? */ tkci_obtainwait(ci, class, TK_LOCKVARADDR); TK_LOCK(ci->tkci_lock); goto tryagain; case TK_CL_WILLOBTAIN: case TK_CL_SWILLOBTAIN: case TK_CL_REJECTED: tkci_scwait(ci, class, TK_LOCKVARADDR); /* something has changed - try again */ TK_LOCK(ci->tkci_lock); goto tryagain; default: tkci_badstate("tkc_returning", ci, class); /* NOTREACHED */ } CHECK(tstate); } TK_TRACE(TKC_ERETURNING, 0, TK_GETCH(ci->tkci_obj), *ok, TK_NULLSET, TK_NULLSET, ci, TK_METER_VAR); TK_UNLOCK(ci->tkci_lock); } /* * Clear state and destroy. */ void tkc_free( tkc_state_t *cs) /* object to be freed */ { tkci_cstate_t *ci = (tkci_cstate_t *)cs; int i; for (i = 0; i < ci->tkci_ntokens; i++) { ci->tkci_state[i].cl_word = 0; ci->tkci_state[i].cl_state = TK_CL_IDLE; } if (ci->tkci_if == &tkc_local_iface) tkc_destroy_local(cs); else tkc_destroy(cs); } /* * Copy token state. */ void tkc_copy( tkc_state_t *src_cs, tkc_state_t *dst_cs) { tkci_cstate_t *src_ci = (tkci_cstate_t *)src_cs; tkci_cstate_t *dst_ci = (tkci_cstate_t *)dst_cs; int i; for (i = 0; i < src_ci->tkci_ntokens; i++) { dst_ci->tkci_state[i].cl_word = src_ci->tkci_state[i].cl_word; } } #ifdef _KERNEL #define OUTPUT qprintf( void tkc_print(tkc_state_t *cs, char *fmt, ...) #else #define OUTPUT fprintf(f, void tkc_print(tkc_state_t *cs, FILE *f, char *fmt, ...) #endif { tkci_cstate_t *ci = (tkci_cstate_t *)cs; tkci_clstate_t tstate; int i; char buf[512], *p; va_list ap; p = buf; if (fmt) { va_start(ap, fmt); vsprintf(p, fmt, ap); p += strlen(p); } p = __tk_sprintf(p, "obj 0x%x ref %d ntokens %d origcell %d\n", ci->tkci_obj, ci->tkci_ref, ci->tkci_ntokens, #if (_MIPS_SZPTR == 64) ci->tkci_origcell #else 0 #endif ); for (i = 0; i < ci->tkci_ntokens; i++) { tstate = ci->tkci_state[i]; p = __tk_sprintf(p, "\tclass %d:level %s hold %d state %s mrecall %s obtain %s\n", i, __tk_levtab[tstate.cl_extlev], tstate.cl_hold, __tkci_prstate(tstate), __tk_levtab[tstate.cl_mrecall], __tk_levtab[tstate.cl_obtain]); } OUTPUT buf); #if TK_METER if (ci->tkci_flags & TKC_METER) { tk_meter_t *tm = tkc_meter(cs); OUTPUT " meter @0x%p tag 0x%lx name \"%s\"\n", tm, tm->tm_tag, tm->tm_name); for (i = 0; i < ci->tkci_ntokens; i++) { OUTPUT " Cl:%d acq %d cached %d conflict %d outbound %d\n", i, TKC_GET_METER(tm, i, acquire), TKC_GET_METER(tm, i, acq_cached), TKC_GET_METER(tm, i, acq_conflict), TKC_GET_METER(tm, i, acq_outbound)); OUTPUT " recall %d return %d\n", TKC_GET_METER(tm, i, recall), TKC_GET_METER(tm, i, return)); } } #endif } static char *cstates[] = { "BAD-0", "IDLE", "OWNED", "CACHED", "REVOKING", "OBTAINING", "ALREADY", "WILLRETURN", "RETURNING", "WILLOBTAIN", "BORROWED", "BORROWING", "REJECTED", "BREVOKING", "SREVOKING", "SOBTAINING", "COBTAINING", "BOBTAINING", "SWILLOBTAIN", "CREVOKING", "CRESTORE", "SRESTORE", "BRESTORE", }; char * __tkci_prstate(tkci_clstate_t s) { return (cstates[s.cl_state]); } static void tkci_badstate(char *s, tkci_cstate_t *ci, int class) { tkci_clstate_t tstate; tstate = ci->tkci_state[class]; __tk_fatal("%s - bad state %s for class %d - ci 0x%x\n", s, __tkci_prstate(tstate), class, ci); /* NOTREACHED */ } /* * tkci_obtainwait - wait until a token is obtained - there is already a * client that has done the appropriate RPC * The token object lock is held across until we sleep. */ /* ARGSUSED */ static void tkci_obtainwait(tkci_cstate_t *ci, int class, int *l) { tk_list_t *tl; TK_LOCKDECL; TK_LOCK(obtainlock); for (tl = obtainhead.tk_next; tl != &obtainhead; tl = tl->tk_next) { if (tl->tk_tag1 == (void *)ci && tl->tk_tag2 == class) break; } if (tl != &obtainhead) { /* someone already waiting - queue up on that sema */ TK_ASSERT(tl->tk_nwait > 0); tl->tk_nwait++; TK_UNLOCK(obtainlock); } else { /* we can unlock the obtainlock across the malloc, etc * since we are protected via the token object lock. * this means that noone can attempt to wake us. */ TK_UNLOCK(obtainlock); /* XXX don't malloc with lock held */ tl = TK_NODEMALLOC(sizeof(*tl)); tl->tk_tag1 = (void *)ci; tl->tk_tag2 = class; tl->tk_nwait = 1; TK_CREATE_SYNC(tl->tk_wait); /* link onto global class wait list */ TK_LOCK(obtainlock); tl->tk_next = obtainhead.tk_next; tl->tk_prev = &obtainhead; obtainhead.tk_next->tk_prev = tl; obtainhead.tk_next = tl; TK_UNLOCK(obtainlock); } TK_UNLOCKVAR(ci->tkci_lock, *l); /* wait */ TK_SYNC(tl->tk_wait); TK_LOCK(obtainlock); TK_ASSERT(tl->tk_nwait > 0); if (--tl->tk_nwait == 0) { TK_DESTROY_SYNC(tl->tk_wait); TK_NODEFREE(tl); } TK_UNLOCK(obtainlock); } /* * tkci_obtainwake - wake all threads waiting for an obtain */ static void tkci_obtainwake(tkci_cstate_t *ci, int class) { int i; tk_list_t *tl; TK_LOCKDECL TK_LOCK(obtainlock); for (tl = obtainhead.tk_next; tl != &obtainhead; tl = tl->tk_next) { if (tl->tk_tag1 == (void *)ci && tl->tk_tag2 == class) break; } if (tl == &obtainhead) { TK_UNLOCK(obtainlock); return; } /* remove from list */ tl->tk_next->tk_prev = tl->tk_prev; tl->tk_prev->tk_next = tl->tk_next; TK_ASSERT(tl->tk_nwait > 0); /* wake them all up */ for (i = 0; i < tl->tk_nwait; i++) TK_WAKE(tl->tk_wait); TK_UNLOCK(obtainlock); } #if TK_DEBUG static int tkci_nobtainwaiting(tkci_cstate_t *ci, int class) { int i; tk_list_t *tl; TK_LOCKDECL TK_LOCK(obtainlock); for (tl = obtainhead.tk_next; tl != &obtainhead; tl = tl->tk_next) { if (tl->tk_tag1 == (void *)ci && tl->tk_tag2 == class) break; } if (tl == &obtainhead) { TK_UNLOCK(obtainlock); return 0; } i = tl->tk_nwait; TK_UNLOCK(obtainlock); return i; } #endif /* * tkci_scwait - wait for status change * The token object lock is held across until we sleep. */ /* ARGSUSED */ static void tkci_scwait(tkci_cstate_t *ci, int class, int *l) { tk_list_t *tl; TK_LOCKDECL tkci_clstate_t *tstate; tstate = &ci->tkci_state[class]; if (tstate->cl_scwait == 1) { /* someone already waiting - queue up on that sema */ TK_LOCK(sclock); for (tl = schead.tk_next; tl != &schead; tl = tl->tk_next) { if (tl->tk_tag1 == (void *)ci && tl->tk_tag2 == class) break; } TK_ASSERT(tl != &schead); TK_ASSERT(tl->tk_nwait > 0); tl->tk_nwait++; TK_UNLOCK(sclock); } else { /* XXX don't malloc with lock held */ tl = TK_NODEMALLOC(sizeof(*tl)); tl->tk_tag1 = (void *)ci; tl->tk_tag2 = class; tl->tk_nwait = 1; TK_CREATE_SYNC(tl->tk_wait); /* link onto global status change wait list */ TK_LOCK(sclock); tl->tk_next = schead.tk_next; tl->tk_prev = &schead; schead.tk_next->tk_prev = tl; schead.tk_next = tl; TK_UNLOCK(sclock); tstate->cl_scwait = 1; } TK_UNLOCKVAR(ci->tkci_lock, *l); /* wait */ TK_SYNC(tl->tk_wait); TK_LOCK(sclock); TK_ASSERT(tl->tk_nwait > 0); if (--tl->tk_nwait == 0) { TK_DESTROY_SYNC(tl->tk_wait); TK_NODEFREE(tl); } TK_UNLOCK(sclock); } /* * tkci_scwake - wake all threads waiting for a status change * Note that token lock must be held across entire operation */ static void tkci_scwake(tkci_cstate_t *ci, int class) { tk_list_t *tl; int i; TK_LOCKDECL; TK_ASSERT(ci->tkci_state[class].cl_scwait); TK_LOCK(sclock); for (tl = schead.tk_next; tl != &schead; tl = tl->tk_next) { if (tl->tk_tag1 == (void *)ci && tl->tk_tag2 == class) break; } TK_ASSERT(tl != &schead); /* remove from list */ tl->tk_next->tk_prev = tl->tk_prev; tl->tk_prev->tk_next = tl->tk_next; TK_ASSERT(tl->tk_nwait > 0); /* wake them all up */ for (i = 0; i < tl->tk_nwait; i++) TK_WAKE(tl->tk_wait); TK_UNLOCK(sclock); /* noone waiting */ ci->tkci_state[class].cl_scwait = 0; }