Coherent4.2.10/conf/loop/src/loop.c

Compare this file to the similar file:
Show the results in this format:

/*
 * This loop-around driver has been transcribed verbatim from the System V,
 * Release 4 Multiprocessor "Programmer's Guide : STREAMS" manual, from
 * Chapter 4. This functions as a simple test for compatibility.
 *
 * Some minor editing has proved necessary due to this driver's use of header
 * files not documented in the DDI/DKI. The following files were either
 * #included by the original source, but are not present in this system, or
 * were never used in the example. Additional imports not present in the
 * original code are documented below.
 *
 * It may well be that the example is correct in importing these headers, and
 * the "synopsis" lines in the DDI/DKI documentation are incorrect.
 */

#define	_DDI_DKI	1
#define	_SYSV4		1

#include <common/ccompat.h>
#include <sys/types.h>
#include <sys/cred.h>
#include <sys/kmem.h>
#include <sys/ksynch.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <stddef.h>

/* #include "sys/param.h" */
/* #include "sys/sysmacros.h" */
/* #include "sys/dir.h" */
/* #include "sys/signal.h" */
/* #include "sys/user.h" */

#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/ddi.h>


static struct module_info minfo = {
	0xEE12, "loop", 0, INFPSZ, 512, 128
};


/*
 * The original example code contained declarations for the loop... ()
 * routines that did not even declare them as functions, but as integers. This
 * has been corrected. The original code would not have compiled with an ISO
 * C compiler.
 */

static	int	loopopen	__PROTO ((queue_t * q, dev_t * devp, int flag,
					  int sflag, cred_t * credp));
static	int	loopclose	__PROTO ((queue_t * q, int flag,
					  cred_t * credp));
static	void	loopwput	__PROTO ((queue_t * q, mblk_t * mp));
static	void	loopwsrv	__PROTO ((queue_t * q));
static	void	looprsrv	__PROTO ((queue_t * q));

static struct qinit rinit = {
	NULL, looprsrv, loopopen, loopclose, NULL, & minfo, NULL
};

static struct qinit winit = {
	loopwput, loopwsrv, NULL, NULL, NULL, & minfo, NULL
};

struct streamtab loopinfo = { & rinit, & winit, NULL, NULL };


/*
 * Import definitions from the "Space.c" file.
 */

#include "loop.h"

int	loopdevflag = 0;


/*
 * The example code did not declare this, ever.
 */

static	lkinfo_t	loop_lkinfo = {
	"Loopback driver lock"
};


/*
 * Extensions to the original code; as dicussed, there are limitations in the
 * multiprocessor code in the original example. We have extracted the code
 * that deals with the lock hierarchy into separate functions to make it
 * simpler to experiment with different multiprocessor lock strategies.
 */

#define	LOOP_HIER(idx)		(hier + 1)


/*
 * Lock a pair of loop entries.
 */

#if	__USE_PROTO__
static pl_t (LOOP_LOCK) (struct loop * loop, struct loop * other)
#else
static pl_t
LOOP_LOCK __ARGS ((loop, other))
struct loop   *	loop;
struct loop   *	other;
#endif
{
	pl_t		prev_pl;

	/*
	 * Since the entries are part of a single array object, we are
	 * guaranteed that the relational comparison of the pointers will
	 * yield valid results.
	 *
	 * The comparison ensures that we lock the entries in hierarchy order;
	 * the canonical ordering is needed to avoid deadlock.
	 */

	if (loop < other) {

		prev_pl = LOCK (loop->lck, plstr);
		(void) LOCK (other->lck, plstr);
	} else {

		prev_pl = LOCK (other->lck, plstr);
		(void) LOCK (loop->lck, plstr);
	}

        return prev_pl;
}


/*
 * Unlock a pair of loop entries.
 */

#if	__USE_PROTO__
static void (LOOP_UNLOCK) (struct loop * loop, struct loop * other,
			   pl_t prev_pl)
#else
static void
LOOP_UNLOCK __ARGS ((loop, other, prev_pl))
struct loop   *	loop;
struct loop   *	other;
pl_t		prev_pl;
#endif
{
	/*
	 * The original example always unlocked the minor devices in reverse
	 * order to the lock order. This isn't necessary; whether it's a good
	 * idea or not cannot be determined without forbidden knowledge of the
	 * implementation of the lock primitives, or rigorous profiling.
	 */

	UNLOCK (loop->lck, plstr);
	UNLOCK (other->lck, prev_pl);
}


/*
 * Init routine for the driver, called at boot time before system services are
 * initialized. This function may not call any DDI/DKI routines other than
 * those listed on the init(2D2K) manual page, because only those services
 * have been initialized.
 */

__EXTERN_C__	
#if	__USE_PROTO__
void loopinit (void)
#else
void
loopinit ()
#endif
{
	int		hier;

	/*
	 * Allocate a multiprocessor lock for each minor device.
	 *
	 * An arbitrary hierarchy is defined based on position in the
	 * "loop_loop" table, such that to lock two entries at the same time,
	 * always lock "loop_loop [n].lck" before "loop_loop [n+m].lck".
	 */

	/*
	 * This version of the loopback driver will keep the above scheme from
	 * the original code, but there are some important restrictions.
	 *
	 * This scheme restricts the number of minor devices to the range of
	 * valid hierarchy values that LOCK_ALLOC () will accept, which is a
	 * total of 32 entries. This restriction can be lifted by using a
	 * single hierarchy level and using TRYLOCK () when trying to acquire
	 * multiple locks, or simply using a single global lock.
	 *
	 * Note that the original code allocated the locks without testing the
	 * return value from LOCK_ALLOC (). Furthermore, the code passed a
	 * flag value of KM_SLEEP, which is not legal for an init routine,
	 * according to the init(D2DK) manual page.
	 */

	if (loop_cnt > 32) {

		cmn_err (CE_WARN, "Only 32 loopback entries can be provided");
		loop_cnt = 32;
	}

	for (hier = 0 ; hier < loop_cnt ; hier ++) {

		if ((loop_loop [hier].lck =
				LOCK_ALLOC (hier + 1, plstr, & loop_lkinfo,
					    KM_NOSLEEP)) ==  NULL)
			cmn_err (CE_PANIC, "Could not allocate lock in "
					   "loopinit ()");
	}
}


#if	__USE_PROTO__
int loopopen (queue_t * q, dev_t * devp, int __NOTUSED (flag), int sflag,
	      cred_t * __NOTUSED (credp))
#else
int
loopopen (q, devp, flag, sflag, credp)
queue_t	      *	q;
dev_t	      *	devp;
int		flag;
int		sflag;
cred_t	      *	credp;
#endif
{
	struct loop   *	loop;
	dev_t		newminor;

	/*
	 * If CLONEOPEN, pick a minor device number to use. Otherwise, check
	 * the minor device range.
	 */

	if (sflag == CLONEOPEN) {

		for (newminor = 0 ; newminor < loop_cnt ; newminor ++) {

			if (loop_loop [newminor].qptr == NULL)
				break;
		}
	} else
		newminor = geteminor (* devp);

	if (newminor >= loop_cnt)
		return ENXIO;

	/*
	 * Construct new device number if this is the first open.
	 */

	if (q->q_ptr != NULL)
		return 0;

	* devp = makedevice (getemajor (* devp), newminor);

	loop = & loop_loop [newminor];

	q->q_ptr = WR (q)->q_ptr = (char *) loop;
	loop->qptr = WR (q);
	loop->oqptr = NULL;


	/*
	 * Enable put and service routines for this queue pair.
	 */

	qprocson (q);
	return 0;
}


#if	__USE_PROTO__
void loopwput (queue_t * q, mblk_t * mp)
#else
void
loopwput (q, mp)
queue_t	      *	q;
mblk_t	      *	mp;
#endif
{
	struct loop   *	loop;
	pl_t		prev_pl;
	int		error;

	loop = (struct loop *) q->q_ptr;

	switch (mp->b_datap->db_type) {

	case M_IOCTL: {
		struct iocblk *	iocp;

		iocp = (struct iocblk *) mp->b_rptr;

		switch (iocp->ioc_cmd) {

		case LOOP_SET: {
			int		to;	/* other minor device */
			struct loop   *	other;

			/*
			 * Sanity check: "ioc_count" contains the amount of
			 * user-supplied data, and must equal the size of an
			 * int.
			 */

			if (iocp->ioc_count != sizeof (int)) {

				error = EINVAL;
				goto iocnak;
			}


			/*
			 * Fetch other device from 2nd message block.
			 */

			to = * (int *) mp->b_cont->b_rptr;


			/*
			 * More sanity checks: the minor must be in range,
			 * open already, and both devices must be
			 * disconnected.
			 *
			 * Note that the original code contained a serious
			 * flaw in the test logic where the lock on the other
			 * minor device was taken out before the range check.
			 * In addition, the lock was
			 *	oldpri = LOCK (loop_loop [to].qptr, plstr);
			 * but it isn't legal to try locking a "queue_t *".
			 *
			 * The original code allowed a connect attempt to the
			 * same minor as the "loop" entry, which will cause
			 * deadlock.
			 */

			if (to >= loop_cnt || to < 0 ||
			    (other = & loop_loop [to]) == loop) {

				error = ENXIO;
				goto iocnak;
			}

			prev_pl = LOOP_LOCK (loop, other);

			if (other->qptr == NULL || loop->oqptr != NULL ||
			    other->oqptr != NULL) {

				LOOP_UNLOCK (loop, other, prev_pl);
				error = EBUSY;
				goto iocnak;
			}


			/*
			 * Cross-connect streams via the loop structures.
			 */

			loop->oqptr = RD (other->qptr);
			other->oqptr = RD (q);

			LOOP_UNLOCK (loop, other, prev_pl);


			/*
			 * Return a successful ioctl (). Set "ioc_count" to
			 * zero, since no data is returned.
			 */

			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_count = 0;
			break;
		    }

		default:
			error = EINVAL;
iocnak:
			/*
			 * Invalid ioctl (). Setting "ioc_error" causes the
			 * ioctl () call to return that particular errno. By
			 * default, ioctl () will return EINVAL on failure.
			 */

			mp->b_datap->db_type = M_IOCNAK;
			iocp->ioc_error = error;
		}

		qreply (q, mp);
		break;
	    }

	case M_FLUSH: {
		queue_t	      *	tempqptr;

		prev_pl = LOCK (loop->lck, plstr);

		/*
		 * The original example code flushed the read queues of this
		 * minor and any connected minor, despite the fact that there
		 * are never any messages queued there. We'll skip that part.
		 */

		if ((* mp->b_rptr & FLUSHW) != 0)
			flushq (q, FLUSHALL);

		if ((* mp->b_rptr & FLUSHR) != 0)
			if (loop->oqptr != NULL)
				flushq (WR (loop->oqptr), FLUSHALL);

		/*
		 * Now change around the flush message in a similar fashion to
		 * the midpoint of a STREAMS FIFO. The logic in the original
		 * example was broken in that it did not cope correctly with
		 * the situation where the device was not connected, it did
		 * not have a declaration for "tempqptr", and it just fell
		 * through into the data message case.
		 */

		if ((tempqptr = loop->oqptr) != NULL) {

			switch (* mp->b_rptr & ~ FLUSHBAND) {

			case FLUSHW:
			case FLUSHR:
				* mp->b_rptr ^= FLUSHR | FLUSHW;
				break;

			default:
				break;
			}
		} else {
			/*
			 * Canonical driver flush processing. We use a NULL
			 * value in "tempqptr" to flag that the message should
			 * be discarded.
			 */

			* mp->b_rptr &= ~ FLUSHW;

			tempqptr = (* mp->b_rptr & FLUSHR) != 0 ? RD (q) :
						NULL;
		}

		UNLOCK (loop->lck, prev_pl);

		if (tempqptr != NULL)
			putnext (tempqptr, mp);
		else
			freemsg (mp);

		/*
		 * The original example fell through into the data-message
		 * code, which is clearly incorrect.
		 */

		break;
	    }

	case M_DATA:
	case M_PROTO:
	case M_PCPROTO:
		/*
		 * If this stream is not connected, register an error with an
		 * M_ERROR message. Otherwise, queue the message for
		 * forwarding by the write service procedure.
		 */

		prev_pl = LOCK (loop->lck, plstr);

		error = loop->oqptr == NULL ? ENXIO : 0;

		UNLOCK (loop->lck, prev_pl);

		if (error != 0) {

			freemsg (mp);
			putnextctl1 (RD (q), M_ERROR, error);
		} else
			putq (q, mp);
		break;

	default:
		/*
		 * Discard unrecognized messages.
		 */

		freemsg (mp);
		break;
	}
}


#if	__USE_PROTO__
void loopwsrv (queue_t * q)
#else
void
loopwsrv (q)
queue_t	      *	q;
#endif
{
	mblk_t	      *	mp;
	struct loop   *	loop;
	queue_t	      *	tmpqptr;
	pl_t		prev_pl;

	loop = (struct loop *) q->q_ptr;

	while ((mp = getq (q)) != NULL) {

		prev_pl = LOCK (loop->lck, plstr);

		tmpqptr = loop->oqptr;

		UNLOCK (loop->lck, prev_pl);

		/*
		 * The original example code implicitly assumed that the loop
		 * was connected at this point. It also would never pass a
		 * priority message to the other side, and on flow control or
		 * a priority message would never unlock the minor device.
		 *
		 * Note that the disconnect handling in the original example
		 * is broken, and this is only a partial fix.
		 */

		if (tmpqptr == NULL) {

			freemsg (mp);
			putnextctl1 (RD (q), M_ERROR, EINVAL);
			continue;
		}

		if (! pcmsg (mp->b_datap->db_type) &&
		    ! canputnext (tmpqptr)) {

			putbq (q, mp);
			break;
		}

		putnext (tmpqptr, mp);
	}
}


#if	__USE_PROTO__
void looprsrv (queue_t * q)
#else
void
looprsrv (q)
queue_t	      *	q;
#endif
{
	struct loop   *	loop;
	pl_t		prev_pl;

	/*
	 * Deal with being back-enabled by flow control by enabling the write
	 * side service procedure to retry sending the messages backed up
	 * there.
	 */

	loop = (struct loop *) q->q_ptr;

	prev_pl = LOCK (loop->lck, plstr);

	if (loop->oqptr != NULL)
		qenable (WR (loop->oqptr));

	UNLOCK (loop->lck, prev_pl);
}


#if	__USE_PROTO__
int loopclose (queue_t * q, int __NOTUSED (flag), cred_t * __NOTUSED (credp))
#else
int
loopclose (q, flag, credp)
queue_t	      *	q;
int		flag;
cred_t	      *	credp;
#endif
{
	struct loop   *	loop;

	/*
	 * Disable put and service routines for the queue pair. This, plus the
	 * fact that open and close are single-threaded, makes any further
	 * locking of this "loop_loop []" entry unnecessary.
	 */

	qprocsoff (q);

	loop = (struct loop *) q->q_ptr;
	loop->qptr = NULL;


	/*
	 * If we are connected to another stream, break the linkage and send a
	 * hangup message. The original example didn't bother performing any
	 * locking for the disconnect, which is incorrect, because put and
	 * service routines for the other minor device could still be running.
	 */

	if (loop->oqptr != NULL) {
		struct loop   *	other = (struct loop *) loop->oqptr->q_ptr;
		pl_t		prev_pl;

		prev_pl = LOCK (other->lck, plstr);

		if (other->oqptr == WR (q)) {

			putnextctl (loop->oqptr, M_HANGUP);
			other->oqptr = loop->oqptr = NULL;
		}

		UNLOCK (other->lck, prev_pl);
	}

        return 0;
}