/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: stream.c 1.45 89/08/07 $"

/* $Log:	stream.c,v $
 */



/*
 * STREAMS LOW-LEVEL UTILITY ROUTINES
 * 
 * This file contains low-level routines dealing with streams.  It
 * includes routines for bufcall mgt, streams scheduling, event cell
 * allocation/free routines, and various odds and ends.  All the global
 * variables and data structures are also defined in this file.
 *
 * The utilities dealing with queue mgt are in file "str_q.c".
 * The utilities dealing with message mgt are in file "str_mm.c".
 */


#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/signal.h"
#include "sys/mutex.h"
#include "sys/systm.h"
#include "sys/user.h"
#include "sys/sysmacros.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/file.h"
#include "sys/vm.h"
#include "sys/var.h"
#include "sys/buf.h"
#include "sys/engine.h"
#include "sys/slic.h"
#include "sys/slicreg.h"
#include "sys/pte.h"
#include "sys/plocal.h"
#include "sys/gate.h"
#include "sys/intctl.h"
#include "sys/stropts.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/strstat.h"
#include "sys/conf.h"
#include "sys/debug.h"
#include "sys/cmn_err.h"

extern int strlofrac;		/* low priority cutoff percentage */
extern int strmedfrac;		/* medium priority cutoff percentage */
extern int strnsched;		/* number of queues runnable per scheduler kick */

/*
 * arrays to map block classes to real size and weighted size,
 * the latter being used for flow control calculations.
 */
ushort	rbsize[] = {4, 16, 64, 128, 256, 512, 1024, 2048, 4096};
ushort	bsize[]  = {4, 16, 64, 100, 200, 300, 600,  1200, 2400};

queue_t	*queue;			/* queues */
queue_t *qpfreelist;		/* queue-pair freelist */
struct stdata *streams;		/* streams */
struct stdatahdr *strfree;	/* list of free streams */
struct stdatahdr *strhash;	/* hash table of in-use streams */
struct linkblk *linkblk;	/* link blocks */
struct strevent *strevent;	/* event cells */
dblk_t *dblock;			/* dblocks */
mblk_t *mblock;			/* mblocks */
struct queue *qhead;		/* head of queues to run */
struct queue *qtail;		/* last queue */
struct strstat strst;		/* streams statistics structure */
mblk_t *mbfreelist;		/* message block freelist */
dblk_t *dbfreelist[NCLASS+1];	/* data block freelists */
struct dbalcst dballoc[NCLASS+1];/* data block allocation parameters */
struct strevent *sefreelist;	/* stream event cell freelist */
alcdat svcqs;			/* svc scheduler statistics */
alcdat svcruns;			/* svc scheduler statistics */
char strbcflag;			/* bufcall functions ready to go */
static unsigned short bufid;	/* counter for bufcall event identifications */
static struct engine *lastpick; /* used in streams scheduling */

/* Synchronization data structures */

lock_t	bc_mutex;		/* bufcall */
lock_t	ubc_mutex;		/* unbufcall */
lock_t	mb_mutex;		/* msg block mgt */
lock_t	mm_mutex[NCLASS+1];	/* data block mgt */
lock_t	qm_mutex;		/* q_pair mgt */
lock_t	cm_mutex;		/* cell mgt */
lock_t	id_mutex;		/* id mgt */
lock_t	tbl_mutex;		/* stream head table */
lock_t	link_mutex;		/* link table */
lock_t	svc_mutex;		/* svc scheduler */
lock_t	poll_mutex;		/* poll related */
sema_t	svc_sema;		/* svc scheduler */
sema_t	poll_sema;		/* polling process wait on this */

#ifdef DEBUG
int strdebug;			/* turn on debug DPRINTFs */
#endif /* DEBUG */

#ifdef DEBUG
#define LOGMAX 10
int log1[LOGMAX];
int log2[LOGMAX];
int ind1 = 0;
int ind2 = 0;
enqlog(q)
{
	ind1++;
	if (ind1 == LOGMAX) {
		ind1 = 0;
	}
	log1[ind1] = (int)q;
}
deqlog(q)
{
	ind2++;
	if (ind2 == LOGMAX) {
		ind2 = 0;
	}
	log2[ind2] = (int)q;
}
#endif /* DEBUG */

/*
 * Enqueue q at the tail of the service queue.
 * Assumes svc_mutex locked.
 */
svc_enqueue(q)
	register queue_t *q;
{
#ifdef DEBUG
	register queue_t *tmp;

	for (tmp = qhead; tmp; tmp = tmp->q_linkr) {
		ASSERT_DEBUG(tmp != q, "svc_enqueue: bad q");
	}
	enqlog(q);
#endif /* DEBUG */
	if (qhead) {
		/* non-null svc-q */
		qtail->q_linkr = q;
		q->q_linkl = qtail;
	} else {
		/* null svc-q */
		qhead = q;
		q->q_linkl = NULL;
	}
	q->q_linkr = NULL;
	qtail = q;
	BUMPUP(svcqs);
}

/*
 * Dequeue q from the service queue.
 * Could be from the middle of the queue.
 * Assumes svc_mutex locked.
 */
svc_dequeue(q)
	register queue_t *q;
{
#ifdef DEBUG
	register queue_t *tmp;

	ASSERT_DEBUG(qhead && qtail, "svc_dequeue: bad svc_q");
	for (tmp = qhead; tmp; tmp = tmp->q_linkr) {
		if (tmp == q) {
			break;
		}
	}
	ASSERT_DEBUG(tmp == q, "svc_dequeue: q not in svc_q");
	deqlog(q);
#endif /* DEBUG */
	if (q->q_linkl) {
		q->q_linkl->q_linkr = q->q_linkr;
	} else {
		qhead = q->q_linkr;
	}
	if (q->q_linkr) {
		q->q_linkr->q_linkl = q->q_linkl;
	} else {
		qtail = q->q_linkl;
	}
	q->q_linkr = q->q_linkl = NULL;
	svcqs.use--;
#ifdef DEBUG
	if (svcqs.use) {
		ASSERT_DEBUG(qhead && qtail, "svc_dequeue: bad svc_queue");
	}
#endif /* DEBUG */
}


/*
 * Same as qenable.
 *
 * The svc_q assumed to be unlocked.  
 * The stream assumed to be locked.
 */

l_qenable(q)
	register queue_t *q;
{
	register spl_t s_ipl;

	ASSERT_DEBUG((q->q_flag & QUSE), "l_qenable: bad q");

	if (!q->q_qinfo->qi_srvp) {
		return;
	}
	if (q->q_flag & QFREEZE) {
		/* 
		 * This queue should not be enqueued for service now,
		 * since its being frozen.  Just flag it as QTOENAB
		 * which can be used to qenable it later, if desired.
		 */
		q->q_flag |= QTOENAB;
		return;
	}

	s_ipl = p_lock(&svc_mutex, SPLMM);

	/* if already qenabled, return */
	if (q->q_svcflag & QENAB) {
		v_lock(&svc_mutex, s_ipl);
		return;
	}
	q->q_svcflag |= QENAB;
	svc_enqueue(q);

	/* 
	 * If not enough streams queue schedulers running, start one.
	 * Basically this is the place which controls the parallelism
	 * of the streams queues. 
	 *
	 * The following assumes that as many scheduling entities as
	 * there are online processors, may be run in parallel.  If
	 * more sophistication is desired (i.e. only some processors
	 * should be utilized by the streams scheduler) then the
	 * following algorithm should be modified.
	 */
	if (svcruns.use < nonline) {
		v_lock(&svc_mutex, s_ipl);
		setqsched();
	} else {
		v_lock(&svc_mutex, s_ipl);
	}
	q->q_flag &= ~QTOENAB;	/* mutexed by sd_mutex */
}

/*
 * Enable a queue: put it on list of those whose service procedures are ready
 * to run and set up the scheduling mechanism.
 *
 * The svc_q assumed to be unlocked.  
 * The stream is assumed to be unlocked.
 */
qenable(q)
	register queue_t *q;
{
	register spl_t s;

	ASSERT_DEBUG(q, "qenable: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	l_qenable(q);
	v_lock(&q->q_str->sd_mutex, s);
}



/*
 * The pending bufcalls are made, in priority order.  
 *
 * bc_mutex synchronizes list deletion/addition.
 * ubc_mutex synchronizes calling out/unbufcall.
 *
 * The ubc_mutex is held throughout the loop. (This should be examined
 * later for optimization to a finer grain design; although note that
 * bufcalls only happen when memory starts getting exhausted and its
 * not clear that there is much parallelism to gain in that scenario.)
 *
 * Note that the called out function may call bufcall again which will
 * aqcuire bc_mutex.  Thus bc_mutex cannot be held during the calling
 * out regions of this routine.
 *
 * Both ubc_mutex and bc_mutex are assumed to be unlocked. 
 */

bcrun()
{
	register i;
	register int *vnp;
	struct strevent *sep;
	int count, total;
	struct dbalcst *dbp;
	register spl_t s_ipl0, s_iplstr;

	s_ipl0 = p_lock(&ubc_mutex, SPLSTR);

	while (strbcflag) {
		strbcflag = 0;
		for (i = NCLASS, vnp = &v.v_nblkpriv; i >= 0; i--, vnp++) {
			dbp = &dballoc[i];
			s_iplstr = p_lock(&bc_mutex, SPLMM);
			count = *vnp - dbp->dba_cnt;	/* total number of bufs free */
			total = 0;
			while (mbfreelist && (count > 0) && (sep = dbp->dba_hip)) {
				count--;
				total++;
				dbp->dba_hip = sep->se_next;
				v_lock(&bc_mutex, s_iplstr);
				if (sep->se_func) {
					(*sep->se_func)(sep->se_arg);
				}
				sefree(sep);
				s_iplstr = p_lock(&bc_mutex, SPLMM);
			}
			count = (dbp->dba_med - dbp->dba_cnt) - total;
			while (mbfreelist && (count > 0) && (sep = dbp->dba_medp)) {
				count--;
				total++;
				dbp->dba_medp = sep->se_next;
				v_lock(&bc_mutex, s_iplstr);
				if (sep->se_func) {
					(*sep->se_func)(sep->se_arg);
				}
				sefree(sep);
				s_iplstr = p_lock(&bc_mutex, SPLMM);
			}
			count = (dbp->dba_lo - dbp->dba_cnt) - total;
			while (mbfreelist && (count > 0) && (sep = dbp->dba_lop)) {
				count--;
				dbp->dba_lop = sep->se_next;
				v_lock(&bc_mutex, s_iplstr);
				if (sep->se_func) {
					(*sep->se_func)(sep->se_arg);
				}
				sefree(sep);
				s_iplstr = p_lock(&bc_mutex, SPLMM);
			}
			v_lock(&bc_mutex, s_iplstr);
		}
	}
	v_lock(&ubc_mutex, s_ipl0);
}


/*
 * Scheduling loop for queue scheduling and bufcalls.
 * The bufcalls are run first.
 *
 * Assumes no locks held. 
 */

runqueues()
{
	register queue_t *q;
	register spl_t s_ipl;
	register tries = 0;

	/* 
	 * The following if checks are not critical, since there are a
	 * lot of redundant invocations of runqueues; hence they can be
	 * done outside the lock.  The worst that can happen is that the
	 * flag is not set but there is work do be done: well the next
	 * time around the work will be done.  Skipping an iteration
	 * in this manner is harmless and is extremely rare so performance
	 * is not affected.  Checking within the lock is very expensive
	 * since these locks are global to the entire system.
	 */
	if (strbcflag) {
		bcrun();
	}
	if (qhead) {
		/* 
		 * The following loop quits when:
		 *	the number of queues run == strnsched, or
		 *	no queue left in svc_q, or
		 *	all queues in svc_q already busy.
		 */
		while (1) {
			s_ipl = p_lock(&svc_mutex, SPLMM);
			
			if (!(q = qhead)) {
				/* empty svc_q */
				v_lock(&svc_mutex, s_ipl);
				return;
			}
			
			/* 
			 * Get next queue that is not already running.
			 *
			 * Note that only one instance of the service procedure should
			 * run at any time.  Since dequeuing this queue may open up a
			 * window which allows it to remain forever unserviced, it
			 * should be left enqueued for later service.  This is ugly
			 * but safe; this should be a rare situation anyhow.
			 */
		retry:
			if (q->q_svcflag & QSVCBUSY) {
				if (q == qtail) {
					/* all queues are busy */
					v_lock(&svc_mutex, s_ipl);
					return;
				} else {
					/* check next one */
					q = q->q_linkr;
					goto retry;
				}
			} else {
				svc_dequeue(q);
			}
			
			/* 
			 * Unset QENAB (set in qenable) to indicate that its
			 * not in the queue.
			 */
			q->q_svcflag &= ~QENAB;	
			
			/* set QSVCBUSY to indicate that its running */
			q->q_svcflag |= QSVCBUSY;	
			
			BUMPUP(svcruns);
			v_lock(&svc_mutex, s_ipl);
			
			/*
			 * Now run the de-queued queue's service procedure.       
			 * Note that this must be done outside of locks for
			 * parallelization purposes.
			 */
			
			(*q->q_qinfo->qi_srvp)(q);
			
			s_ipl = p_lock(&q->q_str->sd_mutex, SPLSTR);
			
			/* unset QSVCBUSY to indicate that its no longer running */
			(void) p_lock(&svc_mutex, SPLMM);
			q->q_svcflag &= ~QSVCBUSY;	
			svcruns.use--;
			v_lock(&svc_mutex, SPLSTR);
			
			/* check for a potential freeze */
			if (q->q_flag & QFREEZE) {
				(void)cv_sema(&q->q_str->sd_freeze);
			}
			v_lock(&q->q_str->sd_mutex, s_ipl);
			
			if (++tries == strnsched) {	
				/* 
				 * The number of queues to be run per
				 * scheduler invocation has crossed its limit.
				 * This particular invocation of queuerun should
				 * end, but since there may still be work to
				 * do, we kick off another scheduler.  Note
				 * that it does not matter if there is an
				 * extra invocation of queuerun so no locks.
				 */
				setqsched();
				return;
			}
		}
	}
}

#define BUMPENG(eng) {++eng; if (eng == engine_Nengine) eng = engine;}

/*
 * Kick off the scheduling mechanism.
 */
setqsched()
{
	register struct engine *eng, *bestpick;

	P_GATE(G_RUNQ);
	
	/* if lastpick is likely to be busy bump it, else favour it */
	if (svcruns.use > 0) {
		BUMPENG(lastpick);
	}
	bestpick = eng = lastpick;
	while (bestpick->e_pri != PIDLE) {
		BUMPENG(eng);
		if (eng == lastpick) {
			break;
		}
		if ((eng->e_flags & E_NOWAY) == 0 &&
		    (eng->e_state == E_GLOBAL) &&
		    (eng->e_pri > bestpick->e_pri)) {
			bestpick = eng;
		}
	}
	if (bestpick == lastpick) {
		/* this engine has not been checked yet */
		if ((bestpick->e_flags & E_NOWAY) ||
		    (bestpick->e_state != E_GLOBAL)) {
			lastpick = l.eng;
		}
	} else {
		lastpick = bestpick;
	}
	sendsoft(lastpick->e_slicaddr, STRSCHED);
	V_GATE(G_RUNQ);
}


/*
 * Call function 'func' with argument 'arg' when there is a reasonably
 * good chance that a block of size 'size' can be allocated with
 * priority 'pri'.   Returns a unit token value which is zero if this
 * call failed for some reason and which can be used (if non-zero) to 
 * unbufcall this bufcall.
 *
 * Simply links the bufcall in an event cell list corresponding to the
 * buffer size class. The token returned is used to identify the cell
 * an unbufcall() time.
 *
 * bc_mutex synchronizes list deletion/addition.
 * ubc_mutex synchronizes calling out/unbufcall.
 *
 * Assumes that bc_mutex is unlocked (ubc_mutex may be locked).
 */

uint
bufcall(size, pri, func, arg)
	uint size;
	int pri;
	int (*func)();
	long arg;
{
	register spl_t s_ipl;
	register class;
	struct strevent *sep, **tmp;
	struct dbalcst *dbp;
	unsigned short id;

	/*
	 * Fail bufcall if not enough configured buffers for given
	 * priority allocation to ever succeed. 
	 */

	if (size == (uint)SIZE_PRIV) {
		class = NCLASS;
	} else {
		if ((class = getclass((int)size)) >= NCLASS) {
			cmn_err(CE_NOTE, "bufcall: size requested too large\n");
			/*
			 *+ A streams bufcall could not be accepted because the
			 *+ size specified does not correspond to a configured
			 *+ buffer class.
			 */
			return(0);
		}
	}

	/* Check that cutoff is non-zero, otherwise return failure */
	if (bcmax(class, pri) == 0) {
		cmn_err(CE_NOTE, "bufcall: cutoff for this buffer class is zero\n");
		/*
		 *+ A streams bufcall could not be accepted because the
		 *+ maximum allocation cutoff point for the specified
		 *+ buffer class is zero.
		 */
		return(0);	
	}

	/* Check that v.vnblk<class size> is configured to be non-zero */
	if (((pri == BPRI_HI) && (*(&v.v_nblkpriv + (NCLASS - class)) == 0)) ||
	    ((pri == BPRI_MED) && (dballoc[class].dba_med == 0)) ||
	    ((pri == BPRI_LO) && (dballoc[class].dba_lo == 0))) {
		cmn_err(CE_NOTE, "bufcall: no buffers in this class available\n");
		/*
		 *+ A streams bufcall could not be accepted because
		 *+ no buffers are configured to be available for
		 *+ the specified buffer class and priority.
		 */
		return(0);
	}

	if (!(sep = sealloc(SE_NOSLP))) {
		cmn_err(CE_WARN, "bufcall: could not allocate stream event\n");
		/*
		 *+ A bufcall could not be registered due to exhaustion of
		 *+ stream event cells.  This probably implies that the system
		 *+ is under-configured in terms of streams event cells for
		 *+ the given load (check the NSTREVENT configuration parameter).
		 */
		return(0);
	}

	dbp = &dballoc[class];
	s_ipl = p_lock(&bc_mutex, SPLMM);
	tmp = ((pri == BPRI_HI) ? &dbp->dba_hip : 
	       ((pri == BPRI_LO) ? &dbp->dba_lop : &dbp->dba_medp));
	while (*tmp) {
		tmp = &(*tmp)->se_next;
	}
	*tmp = sep;
	sep->se_next = NULL;
	sep->se_func = func;
	sep->se_arg = arg;
	if ((id = ++bufid) == 0) {
		/* skip zero since it is a special return value */
		id = ++bufid;	
	}
	sep->se_id = id;
	v_lock(&bc_mutex, s_ipl);

	/* 
	 * We generate return token by encoding the class, pri and id
	 * to reduce the search time in unbufcall().
	 */
	return(((uint)class << 24) + ((uint)pri << 16) + (uint)id);
}



/*
 * Takes a bufcall event cell from the linked list and returns
 * it to the free list.  The token parameter's upper 8 bits 
 * are the class, the next 8 bits priority and the lower 16 bits
 * are the cell id to be matched.
 *
 * Note that two locks are needed since:
 *	1- bufcall should not fire after the unbufcall has 
 *	   removed the event from the list.
 * 	2- a bufcall() might be invoked by the called out func.
 *
 * Assumes that any private locks which might be held by the
 * function registered for bufcalling is not held by this caller
 * since otherwise a deadlock might happen.
 */

unbufcall(token)
	register uint token;
{
	register spl_t s_ipl1;
	register spl_t s_iplstr;
	register struct strevent *sep, *psep;
	register class, pri, id;
	struct dbalcst *dbp;

	class = token >> 24;
	pri = (token >> 16) & 0xff;
	id = token & 0xffff;
	dbp = &dballoc[class];
	psep = NULL;

	s_ipl1 = p_lock(&ubc_mutex, SPLSTR);
	s_iplstr = p_lock(&bc_mutex, SPLMM);

	/* delete entry from event list for the given class and pri combination */

	sep = ((pri == BPRI_HI) ? dbp->dba_hip : 
	       ((pri == BPRI_LO) ? dbp->dba_lop : dbp->dba_medp));

	while (sep) {
		if (sep->se_id == id) {
			if (psep) {
				psep->se_next = sep->se_next;
			} else {
				if (pri == BPRI_HI) {
					dbp->dba_hip = sep->se_next;
				} else if (pri == BPRI_MED) {
					dbp->dba_medp = sep->se_next;
				} else {
					dbp->dba_lop = sep->se_next;
				}
			}
			sefree(sep);
			break;
		} else {
			psep = sep;
			sep = sep->se_next;
		}
	}

	/* 
	 * If cell not found then bufcall must have already happened
	 * and there is nothing else to do (benign race).
	 */
	v_lock(&bc_mutex, s_iplstr);
	v_lock(&ubc_mutex, s_ipl1);
}



/*
 * Allocate a stream event cell.
 *
 * The current algorithm assumes a static, preallocated freelist.
 * It is unclear whether a more dynamic approach buys all that
 * much extra.  If the system is exhausting event cells, it is
 * probably under-configured and should be "fixed" by re-adjusting
 * the configuration parameters.
 *
 * Assumes event cell mgt is unlocked.
 */

struct strevent *
sealloc(slpflag)
	register slpflag;
{
	register spl_t s_ipl;
	register struct strevent *sep;

#ifdef	lint
	lint_ref_int(slpflag);
#endif	/* lint  */

	s_ipl = p_lock(&cm_mutex, SPLMM);

	if (sefreelist) {
		sep = sefreelist;
		sefreelist = sep->se_next;
		v_lock(&cm_mutex, s_ipl);
		sep->se_func = NULL;
		sep->se_arg = 0;
		sep->se_id = 0;
		sep->se_next = NULL;
		return(sep);
	} else {
		v_lock(&cm_mutex, s_ipl);
		return(NULL);
	}
}


/*
 * Free a stream event cell.
 *
 * Assumes event cell mgt is unlocked.
 */

sefree(sep)
	register struct strevent *sep;
{
	register spl_t s_ipl;

	s_ipl = p_lock(&cm_mutex, SPLMM);
	sep->se_next = sefreelist;
	sefreelist = sep;
	v_lock(&cm_mutex, s_ipl);
}


/* 
 * findmod:  returns index into fmodsw; returns -1 if not found.
 */
int
findmod(name)
	register char *name;
{
	register int i, j;

	for (i = 0; i < fmodcnt; i++) {
		for (j = 0; j < FMNAMESZ + 1; j++) {
			if (fmodsw[i].f_name[j] != name[j]) {
				break;
			}
			if (name[j] == '\0') {
				return(i);
			}
		}
	}
	return(-1);
}

/*
 * Init routine run from main at boot time.  This contains some 
 * machine dependent code.  
 *
 * No mutexing required since called only at boot time.
 */
strinit()
{
	register dblk_t *databp;
	register mblk_t *msgbp;
	register i;
	register struct qinit *tmpinit;
	register class, *vnp;
	register queue_t *qp;
	register struct stdatahdr *stp;
	extern int putnext();

	/* Initialize the various global locks.	 */

	init_lock(&bc_mutex);		/* bufcall */
	init_lock(&ubc_mutex);		/* unbufcall */
	init_lock(&mb_mutex);		/* msg block mgt */
	for (class = 0; class <= NCLASS; class++) { 	/* data blk mgt */
		init_lock(&mm_mutex[class]);		
	}
	init_lock(&qm_mutex);		/* q_pair mgt */
	init_lock(&cm_mutex);		/* cell mgt */
	init_lock(&id_mutex);		/* id mgt */
	init_lock(&tbl_mutex);		/* stream table*/
	init_lock(&link_mutex);		/* stream table*/
	init_lock(&svc_mutex);		/* svc scheduler */
	init_lock(&poll_mutex);		/* poll related */

	/* 
	 * Initialize the semaphore used by the scheduler.
	 */

	init_sema(&svc_sema, 0);
	init_sema(&poll_sema, 0);

	/*
	 * Ensure even number of queues allocated (necessary for allocq).
	 */

	if (v.v_nqueue % 2) {
		cmn_err(CE_CONT, "KERNEL: strinit: odd value configured for v.v_nqueue\n");
		/*
		 *+ The value of configurable parameter v.v_nqueue should be even since
		 *+ stream queues come in pairs.
		 */
		cmn_err(CE_CONT, "KERNEL: strinit: was %d, set to %d", v.v_nqueue, v.v_nqueue-1);
		/*
		 *+ The value of v.v_nqueue had been changed to an even value.
		 */
		v.v_nqueue--;
	}

	queue = (struct queue *)calloc((int) v.v_nqueue * sizeof(struct queue));
	linkblk = (struct linkblk *)calloc((int) nmuxlink * sizeof(struct linkblk));
	strevent = (struct strevent *)calloc((int) nstrevent * sizeof(struct strevent));
	dblock = (struct datab *)calloc((int) ndblock * sizeof(struct datab));
	mblock = (struct msgb *)calloc((int) nmblock * sizeof(struct msgb));

	/* Set up initial queue-pair free list.	 */
	qpfreelist = NULL;
	for (qp = queue; qp < &queue[v.v_nqueue]; qp += 2) {
		qp->q_next = qpfreelist;
		qpfreelist = qp;
	}

	/* Set up initial stream event cell free list.	 */
	sefreelist = NULL;
	for (i = 0; i < nstrevent; i++) {
		strevent[i].se_next = sefreelist;
		sefreelist = &strevent[i];
	}

	/*
	 * Allocate space for streams buffers.  The largest block class
	 * to be allocated must be aligned on a multiple of that class
	 * size, i.e. 4096 byte blocks if allocated must begin on a 4096
	 * byte boundary, 2048 byte blocks on a 2048 byte boundary, etc.
	 */

	if (v.v_nblk4096 > 0) {
		callocrnd(4096);
	} else {
		if (v.v_nblk2048 > 0) {
			callocrnd(2048);
		} else {
			if (v.v_nblk1024 > 0) {
				callocrnd(1024);
			} else {
				callocrnd(512);
				/* sufficient for the rest also */
			}
		}
	}

	/*
	 * Initialize buffers space and set up datablock freelists.
	 * Note that *vnp below indicates the configured number of
	 * buffers for a class.  v_nblkpriv indicates blocks for
	 * use by private buffers.
	 */

	databp = &dblock[0];
	for (class = NCLASS, vnp = &v.v_nblkpriv; class >= 0; class--, vnp++) {
		dbfreelist[class] = NULL;
		dballoc[class].dba_lo = ((*vnp) * strlofrac)/100;
		dballoc[class].dba_med = ((*vnp) * strmedfrac)/100;
		for (i = 0; i < *vnp; i++, databp++) {
			databp->db_class = class;
			if (class == NCLASS) {
				/* private buffer setup */
				databp->db_base = NULL;
				databp->db_lim = NULL;
			} else {
				databp->db_base = (unsigned char *) calloc((int) rbsize[class]);
				databp->db_lim = databp->db_base + rbsize[class];
				ASSERT((unsigned long)(databp->db_lim) < 64*1024*1024, 
				       "KERNEL: strinit: streams buffer address exceeded 64 MB window");
				/*
				 *+ Due to the addressability limitation of many peripherals
				 *+ (for example, MULTIBUS), streams buffers must be allocated within 
				 *+ the first 64 Mbytes of virtual memory.  This limit was
				 *+ exceeded.  Check the NBLK* configuration parameters.
				 */
			}
			databp->db_freep = dbfreelist[class];
			dbfreelist[class] = databp;
		}
	}

	/*
	 * Set up of message block freelist.
	 */

	mbfreelist = NULL;
	for (i = 0; i < nmblock; i++) {
		msgbp = &mblock[i];
		msgbp->b_next = mbfreelist;
		mbfreelist = msgbp;
	}
	/* 
	 * Patch in the alternate q_qinfo values for plugging in the
	 * putnext procedure for all read init structs.  Refer to
	 * comments in str_push().
	 */
	for (i = 0; i < fmodcnt; ++i) {
		tmpinit = fmodsw[i].f_str->st_rdinit;
		tmpinit->_qinit[1] = tmpinit->_qinit[0];
		tmpinit->_qinit[1]._qi_putp = putnext;
	}

	/* initialize streams hash and free tables */
	streams = (struct stdata *)calloc((int) v.v_nstream * sizeof(struct stdata));
	strhash = (struct stdatahdr *)calloc((int) STRHASHSZ * sizeof(struct stdatahdr));
	strfree = (struct stdatahdr *)calloc((int) sizeof(struct stdatahdr));
	for (stp = strfree, i = 0; i < v.v_nstream; stp = (struct stdatahdr *)stp->sd_forw, i++) {
		stp->sd_forw = (struct stdata *)&streams[i];
		stp->sd_forw->sd_back = (struct stdata *)stp;
	}
	streams[v.v_nstream -1].sd_forw = (struct stdata *)strfree;
	strfree->sd_back = (struct stdata *)&streams[v.v_nstream -1];
	for (stp = strhash, i = 0; i < STRHASHSZ ; stp++, i++) {
		stp->sd_forw = stp->sd_back = (struct stdata *)stp;
	}
	lastpick = engine;
}
