1
0
Files
irix-657m-src/irix/cmd/tk/tks.c
2022-09-29 17:59:04 +03:00

2073 lines
53 KiB
C

/**************************************************************************
* *
* 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: tks.c,v 1.54 1997/09/25 20:22:26 henseler Exp $"
#include "tk_private.h"
/*
* Token server implementation
*
* 1) currently tks_obtain waits until the token is obtained then returns.
* this requires alot of threads potentially - should we change it to
* stash away the response message info and return and have another
* callout when we actually get it?
* 2) currently, tks_obtain always will get all tokens - there is
* no communication between tks_cleancell and tks_obtain to
* return immediately with a 'refused' set.
*/
/* list manipulation */
static void tksi_listwait(tk_list_t *, tksi_sstate_t *, int, int *);
static void tksi_listwake(tk_list_t *, tksi_sstate_t *, int);
static tk_list_t classhead; /* class list */
static tk_list_t revokehead; /* revoke list */
static bitlen_t tksi_maxcnum; /* max number for client number */
static sv_t recall_sv;
int __tksi_goffset[TK_MAXTOKENS]; /* offset table for grant bitmaps */
#if !_KERNEL
/* client handle manipulation */
static int tksi_getcnum(tks_ch_t);
static tks_ch_t tksi_getch(int);
typedef struct tksi_chlist_s {
tks_ch_t handle;
int cnum;
struct tksi_chlist_s *next;
} tksi_chlist_t;
static tksi_chlist_t chhead;
static tklock_t chlock;
#else /* _KERNEL */
/* for kernel we assume that cells are small integers */
#define tksi_getcnum(x) x
#define tksi_getch(x) x
#endif /* _KERNEL */
static void tksi_mrecallall(tksi_sstate_t *, tks_ch_t, int, int);
/*
* Some (artificial) limits of the implementation
* MAXCNUM should really be a tuneable..
*/
#define MAXCNUM MAX_CELLS /* max value of client handle num (== cells) */
/* bitmap allocation */
static char *tksi_allocbm(int);
static void tksi_freebm(char *);
static void tksi_initbm(void);
static void tksi_zerobm(char *, int);
#define tksi_sizebm(ntokens) ((ntokens) * ((int)tksi_maxcnum / 8))
/*
* Barriers to synchronize recovery threads with ongoing tks_iterates.
* If a tks_iterate is in progress for a given cell, the mrlock is held in
* MR_ACCESS mode. The recovery thread holds the mrlock in update mode
* to wait for previous tks_iterates to complete.
*/
mrlock_t tks_iterate_barrier[MAXCNUM];
/*
* Per cell initialization
*/
void
tks_init(void)
{
int i;
__tk_lock_init();
revokehead.tk_next = revokehead.tk_prev = &revokehead;
__tk_create_lock(&revokehead.tk_lock);
/*
* must be a multiple of 32 for bitmap to align correctly
*/
tksi_maxcnum = MAXCNUM;
while (tksi_maxcnum & 0x1f)
tksi_maxcnum++;
for (i = 0; i < MAXCNUM; i++)
mrlock_init(&tks_iterate_barrier[i], MRLOCK_DEFAULT,
"iterate_barrier", i);
classhead.tk_next = classhead.tk_prev = &classhead;
__tk_create_lock(&classhead.tk_lock);
/* init grant bitmap offset table */
for (i = 0; i < TK_MAXTOKENS; i++) {
__tksi_goffset[i] = i * ((int)tksi_maxcnum / 8);
}
tksi_initbm();
#if !_KERNEL
chhead.next = NULL;
__tk_create_lock(&chlock);
#endif
#if TK_TRC
__tk_trace_init();
#endif
#if TK_METER
__tk_meter_init();
#endif
#ifndef _KERNEL
__tk_ts_init();
#endif
sv_init(&recall_sv, SV_DEFAULT, "tks_recall");
}
/*
* tks_create - create a token managed object
*/
/* ARGSUSED */
void
tks_create(
char *name,
tks_state_t *ss, /* object state to set up */
void *o, /* server object pointer */
tks_ifstate_t *is, /* interface state */
int ntokens, /* # of tokens in set */
void *tag) /* tag for tracing */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
TK_METER_DECL /* decls 'tm' */
si->tksi_if = is;
si->tksi_obj = o;
si->tksi_ntokens = ntokens;
si->tksi_flags = 0;
si->tksi_nrecalls = 0;
si->tksi_ref = 0;
#if _KERNEL
si->tksi_origcell = cellid();
#endif
bzero(&si->tksi_state[0], ntokens * (int)sizeof(tksi_svrstate_t));
si->tksi_gbits = tksi_allocbm(ntokens);
__tk_create_lock(&si->tksi_lock);
#if TK_METER
tm = NULL;
if (__tk_defaultmeter)
tm = tks_meteron(ss, name, tag);
#endif
TK_TRACE(TKS_CREATE, __return_address, NULL,
TK_NULLSET, TK_NULLSET, TK_NULLSET, si, TK_METER_VAR);
}
/*
* tks_destroy - stop managing an object.
* It is assumed that NO tokens are held and that NO requests for any
* more tokens can come in.
*
* We do have to synchronize with tks_return so that we permit a thread
* in tks_return to complete before we tear everything down. Note
* however that it is illegal to call tks_return AFTER tks_destroy -
* but a thread could be in tks_return.
*/
static int __tk_destroywaits;
void
tks_destroy(
tks_state_t *ss) /* object to be destroyed */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
int i;
TKS_METER_VAR_INIT(tm, ss);
TK_ASSERT((si->tksi_flags & TKS_INRECALL) == 0);
TK_TRACE(TKS_DESTROY, __return_address, NULL, TK_NULLSET, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
while (si->tksi_ref) {
__tk_destroywaits++;
TK_DELAY(1);
}
si->tksi_if = NULL;
si->tksi_obj = NULL;
tstate = &si->tksi_state[0];
for (i = 0; i < si->tksi_ntokens; i++, tstate++) {
TK_ASSERT(tstate->svr_noutreqs == 0);
TK_ASSERT(tstate->svr_lock == 0);
TK_ASSERT(tstate->svr_lockw == 0);
TK_ASSERT(tstate->svr_rwait == 0);
TK_ASSERT(tstate->svr_grants == 0);
TK_ASSERT(tstate->svr_revoke == 0);
TK_ASSERT(tstate->svr_recall == 0);
TK_ASSERT(bftstclr(si->tksi_gbits + __tksi_goffset[i], \
0, tksi_maxcnum) == tksi_maxcnum);
#if TK_METER
if (tm) {
char *rp;
rp = TKS_GET_METER(tm, i, trackcrecall);
if (rp) {
TK_ASSERT(bftstclr(rp, \
0, tksi_maxcnum) == tksi_maxcnum);
tksi_freebm(rp);
}
rp = TKS_GET_METER(tm, i, trackmrecall);
if (rp) {
TK_ASSERT(bftstclr(rp, \
0, tksi_maxcnum) == tksi_maxcnum);
tksi_freebm(rp);
}
}
#endif
}
#if TK_METER
tks_meteroff(ss);
#endif
tksi_freebm(si->tksi_gbits);
si->tksi_ntokens = 0;
__tk_destroy_lock(&si->tksi_lock);
}
/*
* tks_obtain - obtain a set of tokens on behalf of a client.
* Does not return until all tokens are either granted, or the client
* already has them.
*/
void
tks_obtain(
tks_state_t *ss, /* object containing token */
tks_ch_t h, /* handle for client making request */
tk_set_t tw, /* tokens wanted */
tk_set_t *tg, /* tokens granted */
tk_set_t *tr, /* tokens refused */
tk_set_t *th) /* tokens already held by client */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts;
bitnum_t cnum = (bitnum_t)tksi_getcnum(h);
char *bp;
int level, class;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
TK_TRACE(TKS_SOBTAIN, __return_address, h, tw, TK_NULLSET, TK_NULLSET,
si, TK_METER_VAR);
*tg = TK_NULLSET;
*th = TK_NULLSET;
for (ts = tw, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
TKS_INC_METER(tm, class, obtain);
TK_ASSERT((level & level - 1) == 0);
tstate = &si->tksi_state[class];
bp = si->tksi_gbits + __tksi_goffset[class];
TK_LOCK(si->tksi_lock);
/*
* the svr_lock serializes the basic server operations -
* making sure that an operation (such as obtain) completes
* before we start the next operation. Note that any operation
* may require numerous messages and responses to complete
*/
while (tstate->svr_lock) {
tksi_listwait(&classhead, si, class, TK_LOCKVARADDR);
TK_LOCK(si->tksi_lock);
TK_ASSERT(tstate->svr_recall == 0);
}
TK_ASSERT(tstate->svr_recall == 0);
TK_ASSERT((tstate->svr_level & tstate->svr_level - 1) == 0);
if ((level & tstate->svr_level) && level != TK_WRITE) {
/* wants level that is already outstanding
* fine - if we believe client already
* has token add to already-set else
* to granted-set
*/
TK_ASSERT(tstate->svr_grants > 0);
if (btst(bp, cnum)) {
*th |= TK_MAKE(class, level);
} else {
bset(bp, cnum);
tstate->svr_grants++;
*tg |= TK_MAKE(class, level);
}
} else if (tstate->svr_level == 0) {
/* token not given out yet */
TK_ASSERT(bftstclr(bp, 0, tksi_maxcnum) == tksi_maxcnum);
TK_ASSERT(tstate->svr_grants == 0);
bset(bp, cnum);
tstate->svr_grants++;
tstate->svr_level = level;
*tg |= TK_MAKE(class, level);
} else {
/*
* wants conflicting level
* locking the class keeps anyone else from doing
* an obtain on this class.
*/
tstate->svr_lock = 1;
tstate->svr_revoke = 1;
tstate->svr_revinp = 1;
tstate->svr_backoff = 0;
#if TK_METER
if (tm)
TKS_FIELD_METER(tm, class, revracecur) = 0;
#endif
TK_UNLOCK(si->tksi_lock);
/* callback recall function */
tksi_mrecallall(si, h, class, tstate->svr_level);
TK_LOCK(si->tksi_lock);
TK_ASSERT(tstate->svr_recall == 0);
TK_ASSERT(tstate->svr_revoke == 0);
TK_ASSERT(tstate->svr_noutreqs == 0);
TK_ASSERT(tstate->svr_grants == 0);
TK_ASSERT(bftstclr(bp, 0, tksi_maxcnum) == tksi_maxcnum);
/* give out token */
bset(bp, cnum);
tstate->svr_grants++;
tstate->svr_level = level;
*tg |= TK_MAKE(class, level);
TK_ASSERT(tstate->svr_lock);
tstate->svr_lock = 0;
if (tstate->svr_lockw)
tksi_listwake(&classhead, si, class);
/* XXX once we wake how do we make sure that
* obtainer gets its message before someone else
* comes in and starts a revoke sequence??
*/
}
TK_UNLOCK(si->tksi_lock);
}
if (tr)
*tr = TK_SUB_SET(tw, TK_ADD_SET(*tg, *th));
else {
TK_ASSERT(TK_SUB_SET(tw, TK_ADD_SET(*tg, *th)) == TK_NULLSET);
}
TK_TRACE(TKS_EOBTAIN, 0, h, *tg, *th, TK_NULLSET, si, TK_METER_VAR);
return;
}
/*
* tks_obtain_conditional - obtain a set of tokens on behalf of a client.
* Does not return until all mandatory tokens are either granted, or the client
* already has them. Also attempts to get conditional tokens, but not very hard.
*/
void
tks_obtain_conditional(
tks_state_t *ss, /* object containing token */
tks_ch_t h, /* handle for client making request */
tk_set_t tw, /* mandatory tokens */
tk_set_t tc, /* conditional tokens */
tk_set_t *tg, /* tokens granted */
tk_set_t *tr, /* tokens refused */
tk_set_t *th) /* tokens already held by client */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts;
bitnum_t cnum = (bitnum_t)tksi_getcnum(h);
char *bp;
int level, class;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
TK_TRACE(TKS_SCOBTAIN, __return_address, h, tw, tc, TK_NULLSET,
si, TK_METER_VAR);
*tg = TK_NULLSET;
*th = TK_NULLSET;
tks_obtain(ss, h, tw, tg, tr, th);
/*
* Now try for conditional tokens. Only grant those that can be done
* immediately and only if we got all the mandatory ones.
*/
if (tw != *tg) {
if (tr)
*tr = TK_SUB_SET(tw, TK_ADD_SET(*tg, *th));
else {
TK_ASSERT(0);
}
TK_TRACE(TKS_ECOBTAIN, 0, h, *tg, *th, TK_NULLSET, si, TK_METER_VAR);
return;
}
for (ts = tc, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
TKS_INC_METER(tm, class, obtain);
TK_ASSERT((level & level - 1) == 0);
tstate = &si->tksi_state[class];
bp = si->tksi_gbits + __tksi_goffset[class];
TK_LOCK(si->tksi_lock);
/*
* the svr_lock serializes the basic server operations -
* making sure that an operation (such as obtain) completes
* before we start the next operation. Note that any operation
* may require numerous messages and responses to complete
*/
while (tstate->svr_lock) {
tksi_listwait(&classhead, si, class, TK_LOCKVARADDR);
TK_LOCK(si->tksi_lock);
TK_ASSERT(tstate->svr_recall == 0);
}
TK_ASSERT(tstate->svr_recall == 0);
TK_ASSERT((tstate->svr_level & tstate->svr_level - 1) == 0);
if ((level & tstate->svr_level) && level != TK_WRITE) {
/* wants level that is already outstanding
* fine - if we believe client already
* has token add to already-set else
* to granted-set
*/
TK_ASSERT(tstate->svr_grants > 0);
if (btst(bp, cnum)) {
*th |= TK_MAKE(class, level);
} else {
bset(bp, cnum);
tstate->svr_grants++;
*tg |= TK_MAKE(class, level);
}
} else if (tstate->svr_level == 0) {
/* token not given out yet */
TK_ASSERT(bftstclr(bp, 0, tksi_maxcnum) == tksi_maxcnum);
TK_ASSERT(tstate->svr_grants == 0);
bset(bp, cnum);
tstate->svr_grants++;
tstate->svr_level = level;
*tg |= TK_MAKE(class, level);
}
TK_UNLOCK(si->tksi_lock);
}
if (tr)
*tr = TK_SUB_SET(TK_ADD_SET(tw, tc), TK_ADD_SET(*tg, *th));
else {
TK_ASSERT(TK_SUB_SET(TK_ADD_SET(tw, tc), TK_ADD_SET(*tg, *th)) == TK_NULLSET);
}
TK_TRACE(TKS_ECOBTAIN, 0, h, *tg, *th, TK_NULLSET, si, TK_METER_VAR);
return;
}
/*
* tks_is_idle - return 1 if the token server is completely idle, else
* return 0.
*/
int
tks_is_idle(
tks_state_t *ss)
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
int i;
int is_idle = 1;
if ((si->tksi_flags & TKS_INRECALL) != 0)
is_idle = 0;
if (si->tksi_ref)
is_idle = 0;
tstate = &si->tksi_state[0];
for (i = 0; i < si->tksi_ntokens; i++, tstate++) {
if (tstate->svr_noutreqs != 0)
is_idle = 0;
if (tstate->svr_lock != 0)
is_idle = 0;
if (tstate->svr_lockw != 0)
is_idle = 0;
if (tstate->svr_rwait != 0)
is_idle = 0;
if (tstate->svr_grants != 0)
is_idle = 0;
if (tstate->svr_revoke != 0)
is_idle = 0;
if (tstate->svr_recall != 0)
is_idle = 0;
}
return is_idle;
}
/*
* handle 'eh' tokens from a server initiated mandatory recall
* These tokens may be the result of a race where the client is
* in the process of returning the token and we went off and tried to
* recall (mandatory) it.
*/
static int
tksi_sr(
tksi_sstate_t *si, /* object containing token */
bitnum_t cnum,
int class,
int level,
tk_set_t *tset,
tk_disp_t *dofset,
int refused) /* token refused to be returned */
{
tksi_svrstate_t *tstate;
char *bp;
int bk = 0;
TK_METER_DECL /* decls 'tm' */
TKS_METER_VAR_INIT(tm, si);
tstate = &si->tksi_state[class];
TK_ASSERT(!(tstate->svr_recall));
TK_ASSERT(tstate->svr_revoke);
TK_ASSERT(tstate->svr_noutreqs > 0);
/*
* if we still think client has token, send another revoke
* message
*/
bp = si->tksi_gbits + __tksi_goffset[class];
if (btst(bp, cnum) && !refused) {
TK_COMBINE_SET(*tset, TK_MAKE(class, level));
TK_COMBINE_SET(*dofset, TK_MAKE(class, TK_SERVER_MANDATORY));
if (bk < tstate->svr_backoff)
bk = tstate->svr_backoff;
if (tstate->svr_backoff < TK_MAXBACKOFF)
tstate->svr_backoff++;
TKS_INC_METER(tm, class, revrace);
TKS_INC_METER(tm, class, revracecur);
#if TK_DEBUG && !_KERNEL
if (TKS_GET_METER(tm, class, revracecur) > (10 * tstate->svr_noutreqs)) {
/* dump log */
kill(getpid(), SIGUSR2);
fprintf(stderr, "%d:revracecur > 10!\n",
getpid());
/* stop us all! */
sigsend(P_PGID, P_MYID, SIGSTOP);
}
#else
if ((TKS_GET_METER(tm, class, revracecur) % 10) == 0)
cmn_err(CE_WARN, "token server 0x%x revracecur %d\n",
si, TKS_GET_METER(tm, class, revracecur));
#endif
} else {
/* client returned it! great. OR refused */
#if TK_METER
if (tm) {
char *rp = TKS_GET_METER(tm, class, trackmrecall);
TK_ASSERT(rp);
TK_ASSERT(btst(rp, cnum));
bclr(rp, cnum);
}
#endif
TK_ASSERT(tstate->svr_noutreqs > 0);
tstate->svr_noutreqs--;
/*
* we don't need complicated flags as for recall since
* mrecallall is waiting for a single token..
*/
if (!tstate->svr_revinp && tstate->svr_noutreqs == 0) {
TK_ASSERT(refused || bftstclr(bp, 0, tksi_maxcnum) == tksi_maxcnum);
TK_ASSERT(tstate->svr_level == 0);
if (tstate->svr_rwait)
tksi_listwake(&revokehead, si, class);
}
}
return bk;
}
/*
* tksi_rr - called by tks_return
* Check to see it token are ones a recall is pending on and if so checks
* to see if all recalled classes are complete and if so calls the
* (*tks_recalled)() callout
* XXX locking - is this necessary/sufficient??
*/
static int
tksi_rr(
tksi_sstate_t *si, /* object containing token */
bitnum_t cnum,
int class,
int level,
tk_set_t *torec,
tk_disp_t *dofrec,
int *done,
int refused) /* true if token was refused to be returned */
{
tksi_svrstate_t *tstate;
char *bp;
int bk = 0;
TK_METER_DECL /* decls 'tm' */
TKS_METER_VAR_INIT(tm, si);
/*
* handle 'unknown' tokens - these are most likely races.
* If in fact the client has already returned the token
* all is well, otherwise we need to resend the recall msg
*/
tstate = &si->tksi_state[class];
TK_ASSERT(tstate->svr_revoke == 0);
TK_ASSERT(si->tksi_nrecalls > 0);
/*
* if we still think client has token, send another revoke
* message
*/
bp = si->tksi_gbits + __tksi_goffset[class];
if (btst(bp, cnum) && !refused) {
TK_COMBINE_SET(*torec, TK_MAKE(class, level));
TK_COMBINE_SET(*dofrec, TK_MAKE(class, TK_SERVER_CONDITIONAL));
/*
* XXX this isn't the greatest - using a per
* token backoff since it really doesn't make much
* sense when there are multiple tokens
*/
bk = tstate->svr_backoff;
if (tstate->svr_backoff < TK_MAXBACKOFF)
tstate->svr_backoff++;
TKS_INC_METER(tm, class, recrace);
TKS_INC_METER(tm, class, recracecur);
#if TK_DEBUG
#if !_KERNEL
if (TKS_GET_METER(tm, class, recracecur) > 10) {
/* dump log */
kill(getpid(), SIGUSR2);
fprintf(stderr, "%d:recracecur > 10!\n",
getpid());
/* stop us all! */
sigsend(P_PGID, P_MYID, SIGSTOP);
}
#else
if ((TKS_GET_METER(tm, class, recracecur) % 10) == 0)
cmn_err(CE_WARN, "token server 0x%x recrace %d\n",
si, TKS_GET_METER(tm, class, recracecur));
#endif
#endif
} else {
/* client returned it! great. OR refused */
TK_ASSERT(si->tksi_nrecalls > 0);
if (--si->tksi_nrecalls == 0)
*done = 1;
#if TK_METER
if (tm) {
char *rp = TKS_GET_METER(tm, class, trackcrecall);
TK_ASSERT(rp);
TK_ASSERT(btst(rp, cnum));
bclr(rp, cnum);
}
#endif
}
return bk;
}
/*
* tks_return - return a set of tokens.
* This is called when:
* 1) responding to a server initiated conditional recall (tks_recall)
* 2) responding to a server initiated mandatory recall (tks_obtain)
* 3) processing a client initiated return
*
* Note that return operations are always permitted - whether the class is
* locked or not.
*
* Note that a client initiated return always must have a token and
* that the token must always still be valid (have a grant). This is
* since if a return shows up at the client, and the client has already
* started to return the token, an 'eh' response will be generated - so
* the only token that could actually satisfy the return would be the
* client one.
*
* Races - any one of the tokens returned here could be the last outstanding
* token, and could allow another thread to legitimitely call tks_destroy.
* We need some synchronization so that we can continue through this routine
* and acquire/release locks etc w/o accessing freed memory. Note that
* it is illegal to call tks_destroy if there are in fact any outstanding
* tokens so the race is fairly simple to close with a reference count.
*
* Note - only TK_SERVER_CONDITIONAL tokens are permitted in the tref set
* unless we are processing an OOB error.
*/
/* ARGSUSED */
void
tks_return(
tks_state_t *ss, /* object containing token */
tks_ch_t h, /* client handle */
tk_set_t tr, /* tokens to be returned */
tk_set_t tref, /* tokens refused */
tk_set_t teh, /* tokens client didn't think it had */
tk_disp_t why) /* why returning */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts;
tk_disp_t ds;
bitnum_t cnum = (bitnum_t)tksi_getcnum(h);
char *bp;
tk_set_t torecall, tocall;
tk_disp_t dofrecall, dofref;
int i, bk, disp, level, class;
int recalldone;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
TK_TRACE(TKS_SRETURN, __return_address, h, tr, tref, teh,
si, TK_METER_VAR);
TK_TRACE(TKS_SRETURN2, 0, h, TK_NULLSET, TK_NULLSET, why,
si, TK_METER_VAR);
TK_ATOMIC_ADD(&si->tksi_addref, 1);
dofref = why;
recalldone = 0;
tocall = TK_NULLSET;
for (ts = tr, class = 0, ds = why; ts;
ts = ts >> TK_LWIDTH, ds = ds >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
TKS_INC_METER(tm, class, return);
/*
* level could be up to 2 levels - could be returning
* SWRITE|READ and getting WRITE
*/
TK_ASSERT(level != 6 && level != 7 && level != 0);
tstate = &si->tksi_state[class];
bp = si->tksi_gbits + __tksi_goffset[class];
disp = ds & TK_SET_MASK;
TK_ASSERT(disp);
TK_ASSERT((disp & disp - 1) == 0);
TK_LOCK(si->tksi_lock);
TK_ASSERT(disp != TK_SERVER_CONDITIONAL || tstate->svr_recall);
TK_ASSERT(!(tstate->svr_recall && tstate->svr_revoke));
/* make sure consistent revoke state */
TK_ASSERT(!tstate->svr_rwait || tstate->svr_revoke);
TK_ASSERT(disp != TK_SERVER_MANDATORY || tstate->svr_revoke);
/*
* XXX is this ok for recall? the token client is sure
* to set the level to be what it really has but in general
* recall doesn't really talk about levels ..
*/
TK_ASSERT((level & tstate->svr_level) == level);
TK_ASSERT(btst(bp, cnum));
TK_ASSERT(tstate->svr_grants > 0);
bclr(bp, cnum);
if (disp == TK_SERVER_MANDATORY) {
#if TK_METER
if (tm) {
char *rp = TKS_GET_METER(tm, class, trackmrecall);
TK_ASSERT(rp);
TK_ASSERT(btst(rp, cnum));
bclr(rp, cnum);
}
#endif
TK_ASSERT(tstate->svr_noutreqs > 0);
tstate->svr_noutreqs--;
} else if (disp == TK_SERVER_CONDITIONAL) {
if (--si->tksi_nrecalls == 0)
recalldone = 1;
#if TK_METER
if (tm) {
char *rp = TKS_GET_METER(tm, class, trackcrecall);
TK_ASSERT(rp);
TK_ASSERT(btst(rp, cnum));
bclr(rp, cnum);
}
#endif
}
if (--tstate->svr_grants == 0) {
TK_ASSERT(bftstclr(bp, 0, tksi_maxcnum) == tksi_maxcnum);
tstate->svr_level = 0;
if (tstate->svr_noteidle) {
tstate->svr_noteidle = 0;
TK_COMBINE_SET(tocall, TK_MAKE(class, TK_READ));
}
/*
* we don't need complicated flags
* as for recall since
* mrecallall is waiting for a single token..
*/
if (disp == TK_SERVER_MANDATORY) {
/*
* With positive Ack protocol - a client
* initiated return can't possibly be
* the last msg in
*/
if (!tstate->svr_revinp &&
tstate->svr_noutreqs == 0) {
if (tstate->svr_rwait)
tksi_listwake(&revokehead, si,
class);
}
}
} else {
TK_ASSERT(tstate->svr_level != TK_WRITE);
}
TK_UNLOCK(si->tksi_lock);
}
/*
* now take care of 'unknown' tokens
*/
torecall = TK_NULLSET;
dofrecall = TK_NULLSET;
TK_LOCK(si->tksi_lock);
for (ts = teh, class = 0, ds = why; ts;
ts = ts >> TK_LWIDTH, ds = ds >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
disp = ds & TK_SET_MASK;
TK_ASSERT(disp);
TK_ASSERT((disp & disp - 1) == 0);
if (disp == TK_SERVER_MANDATORY)
bk = tksi_sr(si, cnum, class, level,
&torecall, &dofrecall, 0);
else if (disp == TK_SERVER_CONDITIONAL)
bk = tksi_rr(si, cnum, class, level,
&torecall, &dofrecall, &recalldone, 0);
else
TK_ASSERT(0);
}
TK_UNLOCK(si->tksi_lock);
/*
* the result of going through the 'eh' tokens will be some to
* re-transmit or perhaps a sign that we are done
*/
if (torecall) {
if (bk)
tk_backoff(bk);
TK_TRACE(TKS_RECALL_CALL, 0, h, torecall, TK_NULLSET,
dofrecall, si, TK_METER_VAR);
si->tksi_if->tks_recall(si->tksi_obj, h, torecall, dofrecall);
}
/*
* go through refused tokens.
*/
TK_LOCK(si->tksi_lock);
for (ts = tref, class = 0, ds = dofref; ts;
ts = ts >> TK_LWIDTH, ds = ds >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
disp = ds & TK_SET_MASK;
TK_ASSERT(disp);
TK_ASSERT((disp & disp - 1) == 0);
if (disp == TK_SERVER_MANDATORY)
bk = tksi_sr(si, cnum, class, level,
NULL, NULL, 1);
else if (disp == TK_SERVER_CONDITIONAL)
bk = tksi_rr(si, cnum, class, level,
NULL, NULL,
&recalldone, 1);
else
TK_ASSERT(0);
}
TK_UNLOCK(si->tksi_lock);
/*
* If idle callout needs to happen do so
*/
if (tocall) {
TK_TRACE(TKS_IDLE_CALL, 0, 0, tocall, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
si->tksi_if->tks_idle(si->tksi_obj, tocall);
}
/*
* no longer need reference - if recalledone is set then there
* is no legitimate way that tks_destroy could have been called
* And if recalldone is not set, we NEVER touch 'si' again.
*/
TK_ASSERT(si->tksi_ref > 0);
TK_ATOMIC_ADD(&si->tksi_addref, -1);
/*
* we call (*tks_recalled) only when all recalled tokens have been
* dispositioned. We don't require a single call to do all that
*/
if (recalldone) {
tk_set_t tdone = TK_NULLSET;
tk_set_t tndone = TK_NULLSET;
if (si->tksi_flags & TKS_SYNCRECALL) {
TK_WAKERECALL(si);
return;
}
tstate = &si->tksi_state[0];
TK_LOCK(si->tksi_lock);
for (i = 0; i < si->tksi_ntokens; i++, tstate++) {
if (tstate->svr_recall) {
if (tstate->svr_grants == 0)
TK_COMBINE_SET(tdone, TK_MAKE(i, TK_READ));
else
TK_COMBINE_SET(tndone, TK_MAKE(i, TK_READ));
tstate->svr_recall = 0;
tstate->svr_lock = 0;
if (tstate->svr_lockw)
tksi_listwake(&classhead, si, i);
}
#if TK_METER
if (tm && TKS_FIELD_METER(tm, i, trackcrecall))
TK_ASSERT( \
bftstclr(TKS_FIELD_METER(tm,\
i, trackcrecall), 0, \
tksi_maxcnum) == tksi_maxcnum);
#endif
}
TK_ASSERT((tdone | tndone) != TK_NULLSET);
si->tksi_flags &= ~(TKS_INRECALL);
TK_UNLOCK(si->tksi_lock);
TK_TRACE(TKS_RECALLED, 0, 0, tdone, tndone,
TK_NULLSET, si, TK_METER_VAR);
si->tksi_if->tks_recalled(si->tksi_obj, tdone, tndone);
/* WARNING - entire tks may be torn down when we return! */
}
}
/*
* tks_recall - recall a set of tokens
* This is used when the server object manager wishes to shut down
* an object.
* Note that NO locking is done - any client who knows
* how can still acquire tokens.
* This routine calls out to the (*tks_recall) callout for each
* client that has the token AT THE TIME tks_recall() is called.
* It then returns.
* When a response from all clients arrive, the (*tks_recalled) callout
* is called with the set of tokens that has been successfully recalled,
* as well as the set which weren't successfully recalled.
* The token sets passed to tks_recalled() are up to date - i.e.
* if an obtain for a particular class came in during the recall operation,
* the 'refused' set will contain that class..
*/
void
tks_recall(
tks_state_t *ss, /* object containing token */
tk_set_t tset, /* tokens to be recalled */
tk_set_t *refused) /* Tokens refused, if null recall is async */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts, tr;
tk_disp_t dofrec;
char *bp, *cp;
int didrecalls = 0, class;
tks_ch_t client;
bitnum_t b, remain, bv;
bitnum_t lastb = tksi_maxcnum;
char *mp[TK_MAXTOKENS];
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
TK_LOCK(si->tksi_lock);
TK_TRACE(TKS_SRECALL, __return_address, 0, tset, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
/* XXX later - just queue them */
TK_ASSERT((si->tksi_flags & (TKS_INRECALL)) == 0);
si->tksi_flags |= TKS_INRECALL;
if (refused)
si->tksi_flags |= TKS_SYNCRECALL;
TK_ASSERT(si->tksi_nrecalls == 0);
TK_UNLOCK(si->tksi_lock);
/*
* go through and tag each token to be recalled - we must do
* this before actually doing any callouts since a callout
* could end up calling tks_return which must know
* whether all the tokens to be recalled are finished
* We keep track of each client that needs a message
* to speed things up in the real pass
*
* cp tracks all clients that need a msg
* mp tracks clients per token
*/
bzero(mp, sizeof(mp));
cp = tksi_allocbm(1);
TK_LOCK(si->tksi_lock);
for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((ts & TK_SET_MASK) == 0)
continue;
TK_ASSERT(class < si->tksi_ntokens);
tstate = &si->tksi_state[class];
TK_ASSERT(tstate->svr_lock == 0);
TK_ASSERT(tstate->svr_lockw == 0);
TK_ASSERT(tstate->svr_revoke == 0);
if (tstate->svr_grants == 0)
/* easy! */
continue;
/* alloc a bitmap for this class to track all clients */
mp[class] = tksi_allocbm(1);
#if TK_METER
if (tm)
TKS_FIELD_METER(tm, class, recracecur) = 0;
#endif
tstate->svr_recall = 1;
tstate->svr_backoff = 0;
bp = si->tksi_gbits + __tksi_goffset[class];
/*
* walk through grant bitmap for this class
*/
b = 0;
remain = lastb;
while (b < lastb) {
bv = bftstclr(bp, b, remain);
b += bv;
remain -= bv;
/* b now points to set bit unless we're at end */
if (b >= lastb)
break;
si->tksi_nrecalls++;
tstate->svr_lock = 1;
/* mark this client as needing a msg */
bset(mp[class], b);
bset(cp, b);
/* advance past one we just did */
remain--;
b++;
}
}
/*
* if there are any recalls going out then tks_return will be
* the one that ends up calling tks_recalled
*/
if (si->tksi_nrecalls)
didrecalls = 1;
TK_UNLOCK(si->tksi_lock);
/*
* Now that we've unlocked the token, returns, etc. can come in
* But we will send a msg to them regardless ...
*/
/*
* walk through client bitmap
*/
b = 0;
remain = lastb;
while (b < lastb) {
bv = bftstclr(cp, b, remain);
b += bv;
remain -= bv;
/* b now points to set bit unless we're at end */
if (b >= lastb)
break;
tr = TK_NULLSET;
dofrec = TK_NULLSET;
client = tksi_getch((int)b);
/*
* go through class bitmaps to see if this client should
* get a message
*/
for (class = 0; class < TK_MAXTOKENS; class++) {
if (mp[class] == 0)
continue;
if (!btst(mp[class], b))
continue;
#if TK_METER
if (tm) {
/*
* for debugging - track who we've sent
* messages to
*/
char *rp;
rp = TKS_GET_METER(tm, class, trackcrecall);
if (rp == 0) {
rp = tksi_allocbm(1);
TKS_FIELD_METER(tm, class, trackcrecall) = rp;
}
TK_ASSERT(!btst(rp, b));
/*
* alas, we need to lock since a previous
* recall-callout could be in tksi_rr
* turning off a different bit.
*/
TK_LOCK(si->tksi_lock);
bset(rp, b);
TK_UNLOCK(si->tksi_lock);
}
#endif
TK_COMBINE_SET(tr,
TK_MAKE(class, TK_GET_LEVEL(tset, class)));
TK_COMBINE_SET(dofrec,
TK_MAKE(class, TK_SERVER_CONDITIONAL));
}
TK_ASSERT(tr);
TK_TRACE(TKS_RECALL_CALL, 0, client, tr, TK_NULLSET,
dofrec, si, TK_METER_VAR);
/*
* note that this could be the last recall to be sent
* out and could immediately turn around and call
* tks_return which would then call the (*tks_recalled)
* function, thus effectively completing the recall
*/
si->tksi_if->tks_recall(si->tksi_obj, client, tr, dofrec);
/* advance past one we just did */
remain--;
b++;
}
/*
* NOTE: unless didrecalls is 0, the recall might already
* have completed and all our memory de-allocated. So from here on
* out NO touching si...(and therefore no tracing unless !didrecalls)
*/
for (class = 0; class < TK_MAXTOKENS; class++)
if (mp[class])
tksi_freebm(mp[class]);
tksi_freebm(cp);
if (!didrecalls) {
/* easy! */
tk_set_t tdone = tset;
TK_TRACE(TKS_ERECALL, 0, 0, TK_NULLSET, TK_NULLSET, TK_NULLSET,
si, TK_METER_VAR);
TK_LOCK(si->tksi_lock);
si->tksi_flags &= ~TKS_INRECALL;
if (refused)
si->tksi_flags &= ~TKS_SYNCRECALL;
TK_UNLOCK(si->tksi_lock);
TK_ASSERT(si->tksi_nrecalls == 0);
if (refused) {
*refused = TK_NULLSET;
return;
}
TK_TRACE(TKS_RECALLED, 0, 0, tdone, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
si->tksi_if->tks_recalled(si->tksi_obj, tdone, TK_NULLSET);
} else if (refused) {
tk_set_t tndone = TK_NULLSET;
int i;
TK_LOCK(si->tksi_lock);
while (si->tksi_nrecalls > 0) {
TK_WAITRECALL(si);
}
tstate = &si->tksi_state[0];
for (i = 0; i < si->tksi_ntokens; i++, tstate++) {
if (tstate->svr_recall) {
if (tstate->svr_grants != 0)
TK_COMBINE_SET(tndone,
TK_MAKE(i, TK_READ));
tstate->svr_recall = 0;
tstate->svr_lock = 0;
if (tstate->svr_lockw)
tksi_listwake(&classhead, si, i);
}
}
si->tksi_flags &= ~(TKS_INRECALL|TKS_SYNCRECALL);
TK_UNLOCK(si->tksi_lock);
*refused = tndone;
/* return */
}
}
/*
* tks_iterate - call specified callout for each client that has
* any of the specified tokens
*
* Unlike recall, we're not quite as concerned about atomic snapshots.
* We do, in an atomic fashion, record all clients that have one of the passed
* token classes. But then we releases the lock, so its possible that
* the client has dropped the token or is in the process of dropping the token.
*/
tks_iter_t
tks_iterate(
tks_state_t *ss, /* object containing token */
tk_set_t tset, /* tokens to search for */
int flag, /* flag */
tks_iter_t (*func)(
void *obj,
tks_ch_t h, /* client handle */
tk_set_t tset, /* tokens client owns */
va_list ap), /* args */
...) /* args */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts, tr;
char *bp, *cp;
int restart, class, stillclients;
tks_iter_t rv;
tks_ch_t h;
bitnum_t b, remain, bv;
bitnum_t lastb = tksi_maxcnum;
int stable = flag & TKS_STABLE;
va_list ap;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
va_start(ap, func);
TK_LOCK(si->tksi_lock);
TK_TRACE(TKS_ITERATE, __return_address, 0, tset, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
if (stable)
TK_ASSERT(si->tksi_nrecalls == 0);
TK_UNLOCK(si->tksi_lock);
/*
* go through and tag each token to be iterated on
* cp tracks all clients that need a msg
*/
cp = tksi_allocbm(1);
TK_LOCK(si->tksi_lock);
for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((ts & TK_SET_MASK) == 0)
continue;
tstate = &si->tksi_state[class];
if (stable) {
TK_ASSERT(tstate->svr_lock == 0);
TK_ASSERT(tstate->svr_lockw == 0);
TK_ASSERT(tstate->svr_revoke == 0);
}
if (tstate->svr_grants == 0)
/* easy! */
continue;
bp = si->tksi_gbits + __tksi_goffset[class];
/*
* walk through grant bitmap for this class
*/
b = 0;
remain = lastb;
while (b < lastb) {
bv = bftstclr(bp, b, remain);
b += bv;
remain -= bv;
/* b now points to set bit unless we're at end */
if (b >= lastb)
break;
/* mark this client as needing a msg */
bset(cp, b);
/* advance past one we just did */
remain--;
b++;
}
}
TK_UNLOCK(si->tksi_lock);
/*
* walk through client bitmap
*/
restart = 1;
rv = TKS_CONTINUE;
stillclients = 1;
while (rv != TKS_STOP && stillclients) {
if (restart) {
b = 0;
remain = lastb;
restart = 0;
stillclients = 0;
}
bv = bftstclr(cp, b, remain);
b += bv;
remain -= bv;
/* b now points to set bit unless we're at end */
if (b >= lastb) {
restart = 1;
continue;
}
h = tksi_getch((int)b);
stillclients = 1;
/*
* go through class bitmaps to see if this client should
* get a message
*/
tr = TK_NULLSET;
mrlock(&tks_iterate_barrier[h], MR_ACCESS, PZERO);
for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((ts & TK_SET_MASK) == 0)
continue;
if (!btst(si->tksi_gbits + __tksi_goffset[class], b))
continue;
TK_COMBINE_SET(tr, TK_MAKE(class, ts & TK_SET_MASK));
}
TK_TRACE(TKS_ITERATE_CALL, 0, h, tr, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
rv = (*func)(si->tksi_obj, h, tr, ap);
mrunlock(&tks_iterate_barrier[h]);
if (rv != TKS_RETRY) {
/* client is done */
bclr(cp, b);
}
/* advance past one we just did */
remain--;
b++;
if (b >= lastb)
restart = 1;
}
tksi_freebm(cp);
return rv;
}
/*
* tks_cleancell - For the passed in cell, clear any grants it has
* corresponding to the passed in tk_set_t.
* This routine handles 'returning' the token if it has an outstanding
* recall, thus the (*recalled) callout or (*obtain) could
* be called as the result of this call.
*
* Note: it is assumed that the selected cell is down and that NO more
* messages from that cell will be coming in and that any
* attempts to send a message will result in an immediate error.
*
* Note: there are no interlocks between this and tks_destroy.
*
* For this call, it is permissible to pass multiple levels per class
*/
void
tks_cleancell(
tks_state_t *ss, /* object containing token */
tks_ch_t h, /* handle for client to clean */
tk_set_t tw, /* tokens to clean */
tk_set_t *tc, /* tokens cleaned */
int flags) /* flags */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tk_set_t ts, tstoret, tstoref;
tk_disp_t dofts;
bitnum_t cnum = (bitnum_t)tksi_getcnum(h);
char *bp;
int level, class;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
TK_TRACE(TKS_SCLEAN, __return_address, h, tw, TK_NULLSET, TK_NULLSET,
si, TK_METER_VAR);
dofts = TK_NULLSET;
tstoret = TK_NULLSET;
tstoref = TK_NULLSET;
TK_LOCK(si->tksi_lock);
for (ts = tw, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((level = ts & TK_SET_MASK) == 0)
continue;
tstate = &si->tksi_state[class];
bp = si->tksi_gbits + __tksi_goffset[class];
if (!btst(bp, cnum)) {
/* client doesn't have it! */
continue;
}
if (!(level & tstate->svr_level)) {
/* client has it but not at level requested */
continue;
}
TK_ASSERT(tstate->svr_level != 0);
TK_ASSERT((tstate->svr_level & tstate->svr_level - 1) == 0);
if (flags & TKS_CHECK) {
TK_COMBINE_SET(tstoret, TK_MAKE(class, tstate->svr_level));
} else if (tstate->svr_recall) {
/*
* recalls always involve ALL grantees so
* we know that it's reasonable to call tks_return()
* We set the tokens as 'refused' to be recalled
* XXX must sync with an inprogress recall...
*/
TK_COMBINE_SET(dofts, TK_MAKE(class, TK_SERVER_CONDITIONAL));
TK_COMBINE_SET(tstoref, TK_MAKE(class, tstate->svr_level));
} else if (tstate->svr_revoke) {
/*
* XXX sync with inprogress revoke - really
* need to make sure svr_revinp is 0 before
* proceeding
*/
TK_ASSERT(tstate->svr_revinp == 0);
TK_COMBINE_SET(dofts, TK_MAKE(class, TK_SERVER_MANDATORY));
TK_COMBINE_SET(tstoref, TK_MAKE(class, tstate->svr_level));
} else {
/*
* not being revoked or recalled - return as if
* client asked us to
*/
TK_COMBINE_SET(dofts, TK_MAKE(class, TK_CLIENT_INITIATED));
TK_COMBINE_SET(tstoret, TK_MAKE(class, tstate->svr_level));
}
}
TK_UNLOCK(si->tksi_lock);
if ((tstoret || tstoref) && !(flags & TKS_CHECK))
tks_return(ss, h, tstoret, tstoref, TK_NULLSET, dofts);
TK_TRACE(TKS_ECLEAN, 0, h, tstoret, tstoref, dofts,
si, TK_METER_VAR);
*tc = TK_ADD_SET(tstoret, tstoref);
return;
}
/*
* tks_notify_idle
* Set up to call (*tks_idle) if grant count goes to zero
*/
void
tks_notify_idle(
tks_state_t *ss, /* object containing token */
tk_set_t tset) /* tokens (classes) to be notified */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
int class;
tk_set_t ts, tocall;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, ss);
tocall = TK_NULLSET;
TK_LOCK(si->tksi_lock);
TK_TRACE(TKS_NOTIFY_IDLE, __return_address, 0, tset, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((ts & TK_SET_MASK) == 0)
continue;
tstate = &si->tksi_state[class];
if (tstate->svr_grants == 0) {
TK_ASSERT(tstate->svr_level == 0);
TK_COMBINE_SET(tocall, TK_MAKE(class, TK_READ));
} else {
tstate->svr_noteidle = 1;
}
}
TK_UNLOCK(si->tksi_lock);
/* callout now for any that are already idle */
if (tocall) {
TK_TRACE(TKS_IDLE_CALL, 0, 0, tocall, TK_NULLSET,
TK_NULLSET, si, TK_METER_VAR);
si->tksi_if->tks_idle(si->tksi_obj, tocall);
}
}
void
tks_state(
tks_state_t *ss, /* object containing tokens */
tks_ch_t client, /* client we are interested in */
void (*func)(
void *obj,
tks_ch_t client,
tk_set_t grants,
tk_set_t recalls))
{
tksi_sstate_t *si;
tksi_svrstate_t *tstate;
int class;
tk_set_t granted;
tk_set_t recalled;
char needunlock[TK_MAXTOKENS];
char *bitmap;
TK_LOCKDECL;
si = (tksi_sstate_t *)ss;
granted = TK_NULLSET;
recalled = TK_NULLSET;
TK_LOCK(si->tksi_lock);
for (class = 0; class < si->tksi_ntokens; class++) {
tstate = &si->tksi_state[class];
if (tstate->svr_grants == 0) {
needunlock[class] = 0;
TK_ASSERT(tstate->svr_level == 0);
continue;
}
bitmap = si->tksi_gbits + __tksi_goffset[class];
if (!btst(bitmap, client)) {
/* Client doesn't have it */
needunlock[class] = 0;
continue;
}
ASSERT(tstate->svr_lock == 0);
tstate->svr_lock = 1;
needunlock[class] = 1;
if (tstate->svr_revoke || tstate->svr_recall)
TK_COMBINE_SET(recalled,
TK_MAKE(class, tstate->svr_level));
else
TK_COMBINE_SET(granted,
TK_MAKE(class, tstate->svr_level));
}
TK_UNLOCK(si->tksi_lock);
if (granted == TK_NULLSET && recalled == TK_NULLSET)
return;
(*func)(si->tksi_obj, client, granted, recalled);
TK_LOCK(si->tksi_lock);
for (class = 0; class < si->tksi_ntokens; class++) {
if (needunlock[class]) {
tstate = &si->tksi_state[class];
tstate->svr_lock = 0;
if (tstate->svr_lockw)
tksi_listwake(&classhead, si, class);
}
}
TK_UNLOCK(si->tksi_lock);
}
/*
* tks_bag - return total state of the token service for
* the associated object. An object bag is supplied. This is loadedwith
* with the token state of all tokens and the current clients.
* It is assumed that the object is in a quiesced state when this
* is called so that the state is frozen. This routine is used during
* token server migration.
*/
void
tks_bag(
tks_state_t *ss, /* object to be migrated */
obj_bag_t bag) /* object bag to use */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
int ntokens = si->tksi_ntokens;
obj_bag_put(bag, OBJ_TAG_TKS_NTOKENS,
&si->tksi_ntokens, sizeof(si->tksi_ntokens));
obj_bag_put(bag, OBJ_TAG_TKS_GBITS,
si->tksi_gbits, tksi_sizebm(ntokens));
obj_bag_put(bag, OBJ_TAG_TKS_STATE,
&si->tksi_state[0], ntokens * sizeof(tksi_svrstate_t));
}
void
tks_free(
tks_state_t *ss) /* object to free */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
int ntokens = si->tksi_ntokens;
bzero(&si->tksi_state[0], ntokens * (int)sizeof(tksi_svrstate_t));
tksi_zerobm(si->tksi_gbits, ntokens);
tks_destroy(ss);
}
/*
* tks_unbag - re-establish token server state for an object from
* bagged data.
*/
int
tks_unbag(
tks_state_t *ss, /* object to be migrated */
obj_bag_t bag) /* bag containing bits */
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
uchar_t ntokens;
int error;
obj_bag_get_here(bag, OBJ_TAG_TKS_NTOKENS,
&ntokens, sizeof(ntokens), error);
if (error)
return error;
ASSERT(ntokens == si->tksi_ntokens);
obj_bag_get_here(bag, OBJ_TAG_TKS_GBITS,
si->tksi_gbits, tksi_sizebm(ntokens), error);
if (error)
return error;
obj_bag_get_here(bag, OBJ_TAG_TKS_STATE,
&si->tksi_state[0], ntokens * sizeof(tksi_svrstate_t),
error);
return error;
}
/*
* tks_isclient - return whether a cell is a client of an object.
* This is determined from the grant map for the existence token.
* It is assumed that the existence token is token 0.
*/
int
tks_isclient(
tks_state_t *ss, /* object of interest */
tks_ch_t cell)
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
return btst(si->tksi_gbits, cell);
}
/*
* Server utilities
*/
/*
* tksi_listwait - wait until list goes unlocked
* calles with 'si' lock held.
*/
/* ARGSUSED */
static void
tksi_listwait(tk_list_t *head, tksi_sstate_t *si, int class, int *l)
{
tksi_svrstate_t *tstate;
tk_list_t *tl;
TK_LOCKDECL;
tstate = &si->tksi_state[class];
if ((head == &revokehead && tstate->svr_rwait == 1) ||
(head != &revokehead && tstate->svr_lockw == 1)) {
/* someone already waiting - queue up on that sema */
TK_LOCK(head->tk_lock);
for (tl = head->tk_next; tl != head; tl = tl->tk_next) {
if (tl->tk_tag1 == (void *)si && tl->tk_tag2 == class)
break;
}
TK_ASSERT(tl != head);
TK_ASSERT(tl->tk_nwait > 0);
tl->tk_nwait++;
TK_UNLOCK(head->tk_lock);
} else {
if (head == &revokehead)
tstate->svr_rwait = 1;
else
tstate->svr_lockw = 1;
/* XXX don't malloc with lock held */
tl = TK_NODEMALLOC(sizeof(*tl));
tl->tk_tag1 = (void *)si;
tl->tk_tag2 = class;
tl->tk_nwait = 1;
TK_CREATE_SYNC(tl->tk_wait);
/* link onto global class wait list */
TK_LOCK(head->tk_lock);
tl->tk_next = head->tk_next;
tl->tk_prev = head;
head->tk_next->tk_prev = tl;
head->tk_next = tl;
TK_UNLOCK(head->tk_lock);
}
TK_UNLOCKVAR(si->tksi_lock, *l);
/* wait */
TK_SYNC(tl->tk_wait);
TK_LOCK(head->tk_lock);
TK_ASSERT(tl->tk_nwait > 0);
tl->tk_nwait--;
if (tl->tk_nwait == 0) {
TK_DESTROY_SYNC(tl->tk_wait);
TK_NODEFREE(tl);
}
TK_UNLOCK(head->tk_lock);
}
/*
* tksi_listwake - wake threads waiting for list lock
*/
static void
tksi_listwake(tk_list_t *head, tksi_sstate_t *si, int class)
{
tksi_svrstate_t *tstate;
tk_list_t *tl;
int i;
TK_LOCKDECL;
tstate = &si->tksi_state[class];
#ifdef TK_DEBUG
if (head == &revokehead)
TK_ASSERT(tstate->svr_rwait == 1);
else
TK_ASSERT(tstate->svr_lockw == 1);
#endif
TK_LOCK(head->tk_lock);
for (tl = head->tk_next; tl != head; tl = tl->tk_next) {
if (tl->tk_tag1 == (void *)si && tl->tk_tag2 == class)
break;
}
TK_ASSERT(tl != head);
TK_ASSERT(tl->tk_nwait > 0);
/* remove from list */
tl->tk_next->tk_prev = tl->tk_prev;
tl->tk_prev->tk_next = tl->tk_next;
i = tl->tk_nwait;
if (head == &revokehead)
tstate->svr_rwait = 0;
else
tstate->svr_lockw = 0;
while (i--)
TK_WAKE(tl->tk_wait);
TK_UNLOCK(head->tk_lock);
}
#if !_KERNEL
/*
* convert from client handle to client number and back
*/
static int
tksi_getcnum(tks_ch_t h)
{
static int acnum = 0;
tksi_chlist_t *p;
TK_LOCKDECL;
TK_LOCK(chlock);
for (p = chhead.next; p; p = p->next) {
if (p->handle == h)
break;
}
if (p == NULL) {
/* alloc a new one */
p = TK_NODEMALLOC(sizeof(*p));
p->cnum = acnum++;
p->handle = h;
p->next = chhead.next;
chhead.next = p;
}
TK_UNLOCK(chlock);
TK_ASSERT(p->cnum < MAXCNUM);
return p->cnum;
}
static tks_ch_t
tksi_getch(int cnum)
{
tksi_chlist_t *p;
TK_LOCKDECL;
TK_LOCK(chlock);
for (p = chhead.next; p; p = p->next) {
if (p->cnum == cnum)
break;
}
TK_UNLOCK(chlock);
TK_ASSERT(p);
return p->handle;
}
#endif /* !_KERNEL */
/*
* tksi_mrecallall - loop through all clients that have the token and
* perform the mandatory recall callback
* We don't need any locks since there is a bitmap per class, and class
* is locked so no more grants can occur and we walk through the map just once
*/
/* ARGSUSED */
static void
tksi_mrecallall(tksi_sstate_t *si, tks_ch_t h, int class, int level)
{
tk_set_t ts = TK_MAKE(class, level);
tk_disp_t dofset = TK_MAKE(class, TK_SERVER_MANDATORY);
tksi_svrstate_t *tstate;
TK_METER_DECL /* decls 'tm' */
tks_ch_t th;
char *bp;
bitnum_t b, remain, bv;
bitnum_t lastb = tksi_maxcnum;
TK_LOCKDECL;
TKS_METER_VAR_INIT(tm, si);
tstate = &si->tksi_state[class];
bp = si->tksi_gbits + __tksi_goffset[class];
b = 0;
remain = lastb;
TK_ASSERT(tstate->svr_noutreqs == 0);
TK_ASSERT(tstate->svr_revinp == 1);
while (b < lastb) {
bv = bftstclr(bp, b, remain);
b += bv;
/* b now points to set bit unless we're at end */
if (b >= lastb)
break;
th = tksi_getch((int)b);
/*
* with the positive acknowledge protocol,
* there should be NO way that a revoke needs to
* go to the client that originated the request.
* We can now assert that this doesn't happen - the only
* way it will is if a client violates the return/revoke
* protocol.
*/
TK_ASSERT(th != h);
/* XXX atomic op should work just fine */
TK_LOCK(si->tksi_lock);
tstate->svr_noutreqs++;
TK_UNLOCK(si->tksi_lock);
#if TK_METER
if (tm) {
/*
* for debugging - track who we've sent
* messages to
*/
char *rp;
rp = TKS_GET_METER(tm, class, trackmrecall);
if (rp == 0) {
rp = tksi_allocbm(1);
TKS_FIELD_METER(tm, class, trackmrecall) = rp;
}
TK_ASSERT(!btst(rp, b));
/*
* alas, we need to lock since a previous
* revoke-callout could be in tksi_sr
* turning off a different bit.
*/
TK_LOCK(si->tksi_lock);
bset(rp, b);
TK_UNLOCK(si->tksi_lock);
}
#endif
TKS_INC_METER(tm, class, mrecall);
TK_TRACE(TKS_RECALL_CALL, 0, th, ts, TK_NULLSET,
dofset, si, TK_METER_VAR);
si->tksi_if->tks_recall(si->tksi_obj, th, ts, dofset);
remain -= bv;
/* advance past one we just did */
remain--;
b++;
}
TK_LOCK(si->tksi_lock);
TK_ASSERT(tstate->svr_revinp);
tstate->svr_revinp = 0; /* let wakeups come in */
while (tstate->svr_noutreqs > 0) {
tksi_listwait(&revokehead, si, class, TK_LOCKVARADDR);
TK_LOCK(si->tksi_lock);
}
#if TK_METER
if (tm && TKS_FIELD_METER(tm, class, trackmrecall))
TK_ASSERT(bftstclr(TKS_FIELD_METER(tm, class, trackmrecall), \
0, tksi_maxcnum) == tksi_maxcnum);
#endif
TK_ASSERT(tstate->svr_grants == 0);
TK_ASSERT(tstate->svr_revoke);
tstate->svr_revoke = 0;
tstate->svr_backoff = 0;
/* wake up potential folks in tksi_return */
if (tstate->svr_rwait)
tksi_listwake(&revokehead, si, class);
TK_UNLOCK(si->tksi_lock);
return;
}
#ifdef _KERNEL
#define OUTPUT qprintf(
void
tks_print(tks_state_t *ss, char *fmt, ...)
#else
#define OUTPUT fprintf(f,
void
tks_print(tks_state_t *ss, FILE *f, char *fmt, ...)
#endif
{
tksi_sstate_t *si = (tksi_sstate_t *)ss;
tksi_svrstate_t *tstate;
int i;
va_list ap;
char buf[512], *p;
p = buf;
if (fmt) {
va_start(ap, fmt);
vsprintf(p, fmt, ap);
p += strlen(p);
}
p = __tk_sprintf(p, "obj 0x%x nrecalls %d ntokens %d flags 0x%x ref %d origcell %d\n", si->tksi_obj,
si->tksi_nrecalls, si->tksi_ntokens, si->tksi_flags,
si->tksi_ref, si->tksi_origcell);
OUTPUT buf);
for (i = 0; i < si->tksi_ntokens; i++) {
tstate = &si->tksi_state[i];
p = buf;
#if _KERNEL
p = __tk_sprintf(p, "\tclass %w32d:level %s rwait %w32d idle %w32d lock %w32d lockw %w32d grants %w32d\n",
#else
p = __tk_sprintf(p, "\tclass %d:level %s rwait %d idle %d lock %d lockw %d grants %d\n",
#endif
i,
__tk_levtab[tstate->svr_level],
tstate->svr_rwait,
tstate->svr_noteidle,
tstate->svr_lock,
tstate->svr_lockw,
tstate->svr_grants);
p = __tk_sprintf(p, "\t grants@0x%x crecall %d mrecall %d revinp %d bckoff %d noutreqs %d\n",
si->tksi_gbits,
tstate->svr_recall,
tstate->svr_revoke,
tstate->svr_revinp,
tstate->svr_backoff,
tstate->svr_noutreqs);
p = __tksi_prbits(p, si->tksi_gbits + __tksi_goffset_intr[i],
tksi_maxcnum, "\t grants at cells:", "\n");
OUTPUT buf);
}
#if TK_METER
if (si->tksi_flags & TKS_METER) {
tk_meter_t *tm = tks_meter(ss);
OUTPUT " meter @0x%p tag 0x%lx name \"%s\"\n",
tm, tm->tm_tag, tm->tm_name);
for (i = 0; i < si->tksi_ntokens; i++) {
char *rp;
p = buf;
p = __tk_sprintf(p, " Cl:%d obtain %d return %d revrace %d mrecall %d\n",
i,
TKS_GET_METER(tm, i, obtain),
TKS_GET_METER(tm, i, return),
TKS_GET_METER(tm, i, revrace),
TKS_GET_METER(tm, i, mrecall));
p = __tk_sprintf(p, " recrace %d\n",
TKS_GET_METER(tm, i, recrace));
if ((rp = TKS_GET_METER(tm, i, trackcrecall)))
p = __tksi_prbits(p, rp,
tksi_maxcnum, "\t conditional recalls:", "\n");
if ((rp = TKS_GET_METER(tm, i, trackmrecall)))
p = __tksi_prbits(p, rp,
tksi_maxcnum, "\t mandatory recalls:", "\n");
OUTPUT buf);
}
}
#endif
}
char *
__tksi_prbits(char *p, char *bp, bitnum_t lastb, char *pref, char *suff)
{
int havebits;
bitnum_t b, remain, bv;
havebits = 0;
b = 0;
remain = lastb;
while (b < lastb) {
bv = bftstclr(bp, b, remain);
b += bv;
/* b now points to set bit unless we're at end */
if (b >= lastb)
break;
if (havebits == 0) {
p = __tk_sprintf(p, pref);
havebits = 1;
}
p = __tk_sprintf(p, "%d ", tksi_getch((int)b));
remain -= bv;
/* advance past one we just did */
remain--;
b++;
}
if (havebits && suff)
p = __tk_sprintf(p, suff);
return p;
}
/*
* tksi_allocbm - allocate bitmap for tksi_maxcnum clients
*/
static char *
tksi_allocbm(int nmaps)
{
char *bp;
bp = TK_NODEMALLOC(tksi_sizebm(nmaps));
bzero(bp, tksi_sizebm(nmaps));
return bp;
}
static void
tksi_freebm(char *bp)
{
TK_NODEFREE(bp);
}
static void
tksi_zerobm(char *bp, int nmaps)
{
bzero(bp, tksi_sizebm(nmaps));
}
static void
tksi_initbm(void)
{
}
/*
* tks_make_dispall:
* Create a disposition for all tokens in tset. The disposition is
* passed in disp.
*/
void
tks_make_dispall(tk_set_t tset, int disp, tk_disp_t *tk_disp)
{
tk_set_t ts;
tk_disp_t ds; /* For speed do it locally and then assign to tk_disp */
int class;
ds = TK_NULLSET;
for (ts = tset, class = 0; ts; ts = ts >> TK_LWIDTH, class++) {
if ((ts & TK_SET_MASK) == 0)
continue;
ds = TK_ADD_DISP(ds, TK_MAKE_DISP(class, disp));
}
*tk_disp = ds;
}
/*
* tks_iterate_sync:
* Wait for any pending tks_iterates to complete for a given cell.
* Called by the recovery thread.
*/
void
tks_iterate_wait(cell_t cell)
{
mrlock(&tks_iterate_barrier[cell], MR_UPDATE, PZERO);
mrunlock(&tks_iterate_barrier[cell]);
}