/*
 *  SunOS Multithreaded STREAMS DLPI Driver
 *
 * This file is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify this file without charge, but are not authorized to
 * license or distribute it to anyone else except as part of a product
 * or program developed by the user.
 *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 * This file is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 *
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS FILE
 * OR ANY PART THEREOF.
 *
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even
 * if Sun has been advised of the possibility of such damages.
 *
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

#pragma ident	"@(#)psli.c	1.3	94/06/22 Sun Microsystems Inc."

/*
 * Includes, Declarations and Local Data
 */


#include	<sys/types.h>
#include	<sys/errno.h>
#include	<sys/debug.h>
#include	<sys/stropts.h>
#include	<sys/stream.h>
#include	<sys/strlog.h>
#include	<sys/cmn_err.h>
#include	<sys/kmem.h>
#include	<sys/conf.h>
#include	<sys/ksynch.h>
#include	<sys/stat.h>
#include	<sys/dlpi.h>
#include 	<sys/modctl.h>
#ifdef  KSTAT
#include	<sys/kstat.h>
#endif
#include	<sys/ddi.h>
#include	<sys/sunddi.h>
#include	"psli.h"

/*
 * Function prototypes.
 */

static	int psliidentify(dev_info_t *);
static  int psliprobe(dev_info_t *);
static	int psliattach(dev_info_t *, ddi_attach_cmd_t);
static	int pslidetach(dev_info_t *, ddi_detach_cmd_t);
static	int psliinfo(dev_info_t *, ddi_info_cmd_t, void *,
	void **);
static	int psliopen(queue_t *, dev_t *, int, int, cred_t *);
static	int psliclose(queue_t *, int, cred_t *);
static	int psliwput(queue_t *, mblk_t *);
static	int psliwsrv(queue_t *);
static	void psliproto(queue_t *, mblk_t *);
static	void psliioctl(queue_t *, mblk_t *);
static	void psliareq(queue_t *, mblk_t *);
static	void pslidreq(queue_t *, mblk_t *);
static	void pslidodetach(struct pslistr *);
static	void pslibreq(queue_t *, mblk_t *);
static	void psliubreq(queue_t *, mblk_t *);
static	void psliireq(queue_t *, mblk_t *);
static	void psliponreq(queue_t *, mblk_t *);
static	void pslipoffreq(queue_t *, mblk_t *);
static	void psliemreq(queue_t *, mblk_t *);
static	void pslidmreq(queue_t *, mblk_t *);
static	void pslipareq(queue_t *, mblk_t *);
static	void pslispareq(queue_t *, mblk_t *);
static	void psliudreq(queue_t *, mblk_t *);
static	int pslistart(queue_t *, mblk_t *, struct psli *);
#ifdef	HARDWARE
static	u_int	psliintr();
#endif
static	void	pslireclaim(struct psli *);
static	void	psliwenable(struct psli *);
static	struct pslistr	*psliaccept(struct pslistr *, struct psli *, int,
	struct medium_addr *);
static  void psliread(struct psli *, caddr_t);
static  void pslisendup(struct psli *, mblk_t *, struct pslistr *(*)());
static  mblk_t *psliaddudind(struct psli *, mblk_t *, struct medium_addr *,
	struct medium_addr *, int, ulong);
static  int pslimcmatch(struct pslistr *, struct medium_addr *);
static	int psliinit(struct psli *);
static	void psliuninit(struct psli *pslip);
static	void pslierror(dev_info_t *,  const char *fmt, ...);
static void localmedaddr(struct medium_addr *);
static void pserror(queue_t *, mblk_t *, int);
static void psiocack(queue_t *, mblk_t *, int, int);
static void psiocnak(queue_t *, mblk_t *, int, int);
static mblk_t *psexchange(queue_t *, mblk_t *, int, unsigned char, long);
static void psdlbindack(queue_t *, mblk_t *, u_long, caddr_t, int, int, u_long);
static void psdlokack(queue_t *, mblk_t *, u_long);
static void psdlerrorack(queue_t *, mblk_t *, u_long, u_long, u_long);
static void psdluderrorind(queue_t *, mblk_t *, u_char *, u_long,
	u_long, u_long);
static void psdlphysaddrack(queue_t *, mblk_t *, caddr_t, int);
#ifdef  KSTAT
static  void    pslistatinit(struct psli *);
#endif

#ifdef	_DDICT
#define	ENOSR	63	/* out of streams resources		*/
#endif


/* Local Static def's */

/*
 * XXX Physical Medium broadcast address definition.
 */
static	struct medium_addr	medbroadcastaddr = {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

/*
 * Linked list of "psli" structures - one per device.
 */
static	struct psli *pslidev = NULL;

/*
 * Linked list of active (inuse) driver Streams.
 */
static	struct	pslistr	*pslistrup = NULL;
static	krwlock_t	pslistruplock;


/*
 * Our DL_INFO_ACK template.
 */
static	dl_info_ack_t psliinfoack = {
	DL_INFO_ACK,			/* dl_primitive */
	MEDIUM_MTU,			/* dl_max_sdu */
	0,				/* dl_min_sdu */
	DEVICE_ADDRL,			/* dl_addr_length */
	DL_CSMACD,			/* dl_mac_type */
	0,				/* dl_reserved */
	0,				/* dl_current_state */
	-2,				/* dl_sap_length */
	DL_CLDLS,			/* dl_service_mode */
	0,				/* dl_qos_length */
	0,				/* dl_qos_offset */
	0,				/* dl_range_length */
	0,				/* dl_range_offset */
	DL_STYLE2,			/* dl_provider_style */
	sizeof (dl_info_ack_t),		/* dl_addr_offset */
	DL_VERSION_2,			/* dl_version */
	MEDIUM_ADDRL,			/* dl_brdcst_addr_length */
	sizeof (dl_info_ack_t) + DEVICE_ADDRL,	/* dl_brdcst_addr_offset */
	0				/* dl_growth */
};

/*
 * Allocate and zero-out "number" structures
 * each of type "structure" in kernel memory.
 */
#define	GETSTRUCT(structure, number)   \
	((structure *) kmem_zalloc(\
		(u_int) (sizeof (structure) * (number)), KM_NOSLEEP))

#define	PSLISAPMATCH(sap, type, flags) ((sap == type)? 1 : \
	((flags & SLALLSAP)? 1 : \
	((sap < MEDIUM_MTU) && (sap > 0) && (type < MEDIUM_MTU))? 1 : 0))

/*
 * Defining DEBUG on the compile line (-DDEBUG) will enable debugging
 * statements in this driver.
 * Error message control:
 * 0 = no messages.
 * 1 = Error messages.
 * Can set the value of the debug flag using adb or in /etc/system file
 * with "set psli:pslidebug=<value>
 */

#ifdef  DEBUG
static	int	pslidebug = 1;
#else
static	int	pslidebug = 0;
#endif  DEBUG

static void *psli_state;	/* opaque handle for state structs */

/* Standard Streams declarations */

static	struct	module_info	psliminfo = {
	PSLIIDNUM,	/* mi_idnum */
	PSLINAME,	/* mi_idname */
	PSLIMINPSZ,	/* mi_minpsz */
	PSLIMAXPSZ,	/* mi_maxpsz */
	PSLIHIWAT,	/* mi_hiwat */
	PSLILOWAT,	/* mi_lowat */
};

static	struct	qinit	pslirinit = {
	0,		/* qi_putp */
	0,		/* qi_srvp */
	psliopen,	/* qi_qopen */
	psliclose,	/* qi_qclose */
	0,		/* qi_qadmin */
	&psliminfo,	/* qi_minfo */
	NULL,		/* qi_mstat */
};

static	struct	qinit	psliwinit = {
	psliwput,	/* qi_putp */
	psliwsrv,	/* qi_srvp */
	0,		/* qi_qopen */
	0,		/* qi_qclose */
	0,		/* qi_qadmin */
	&psliminfo,	/* qi_minfo */
	NULL,		/* qi_mstat */
};

static struct	streamtab	psli_info = {
	&pslirinit,	/* st_rdinit */
	&psliwinit,	/* st_wrinit */
	NULL,		/* st_muxrinit */
	NULL,		/* st_muxwrinit */
};


/* Module Loading/Unloading and Autoconfiguration declarations */

/*
 * cb_ops contains the driver entry points and is roughly equivalent
 * to the cdevsw and bdevsw  structures in previous releases.
 *
 * dev_ops contains, in addition to the pointer to cb_ops, the routines
 * that support loading and unloading our driver.
 *
 * Unsupported entry points are set to nodev, except for the poll
 * routine , which is set to nochpoll(), a routine that returns ENXIO.
 */

static struct cb_ops psli_cb_ops = {
	nodev,			/* cb_open */
	nodev,			/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read */
	nodev,			/* cb_write */
	nodev,			/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	&psli_info,		/* cb_stream */
	D_MP | D_NEW,		/* cb_flag */
};

static struct dev_ops psli_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	psliinfo,		/* devo_getinfo */
	psliidentify,		/* devo_identify */
	psliprobe,		/* devo_probe */
	psliattach,		/* devo_attach */
	pslidetach,		/* devo_detach */
	nodev,			/* devo_reset */
	&psli_cb_ops,		/* devo_cb_ops */
	(struct bus_ops *)NULL,	/* devo_bus_ops */
};


/*
 * Module linkage information for the kernel.
 */
static struct modldrv modldrv = {
	&mod_driverops,	/* Type of module.  This one is a driver */
	"Pseudo Streams DLPI Driver 1.4",   /* Description */
	&psli_ops,	/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1, &modldrv, NULL
};

/*
 * Module Loading and Installation Routines .
 */

/*
 * Module Installation
 * Install the driver and "pre-allocate" space for instances of the driver
 */

int
_init(void)
{
	int status;

	if ((status = ddi_soft_state_init(&psli_state,
			sizeof (struct psli), 0)) != 0) {
		if (pslidebug)
			cmn_err(CE_CONT,
			    "psli:_init - soft_state_init failed: 0x%x\n",
			    status);
		return (status);
	}

	/* initialize global locks here */
	rw_init(&pslistruplock, "psli streams linked list lock",
		RW_DRIVER, (void *)NULL);

	return (mod_install(&modlinkage));
}

/*
 * Module Removal
 */

int
_fini(void)
{
	int status;

	/*
	 *  At this point, make sure you have canceled all call backs and
	 *  de-allocated all memory
	 */
	if ((status = mod_remove(&modlinkage)) != 0) {
		if (pslidebug)
			cmn_err(CE_CONT,
			    "psli:_fini- mod_remove failed: 0x%x\n", status);
		return (status);
	}
	ddi_soft_state_fini(&psli_state);

	return (status);
}

/*
 * Return Module Info.
 */

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}



/*
 * ------------------------------------------------------
 *	Autoconfiguration Routines
 */

/*
 * identify(9e) - are we the right driver.
 */
static int
psliidentify(dev_info_t *dip)
{

	if (strcmp(ddi_get_name(dip), "psli") == 0)
		return (DDI_IDENTIFIED);
	else
		return (DDI_NOT_IDENTIFIED);
}


/*
 * XXX Probe to see if device exists.
 * Note: probe() is required for only non-self-identifying devices.
 * Also Probe should not have any side-effects. Just check that this is the
 * right device. Do not set up any structures or initialize anything.
 */

/*ARGSUSED*/
static int
psliprobe(dip)
dev_info_t *dip;
{
#ifdef	HARDWARE
	volatile	struct psliregs *regsp;

	if (ddi_dev_is_sid(dip) == DDI_FAILURE) {
		if (ddi_map_regs(dip, 0, (caddr_t *)&regsp, 0, 0)
			== DDI_SUCCESS) {

			/*
			 * XXX use ddi_pokes(9F) here to check if
			 * write to device succeeds.
			 */
			ddi_unmap_regs(dip, 0, (caddr_t *)&regsp, 0, 0);

		} else {
			if (pslidebug)
				pslierror(dip, "probe failed");
			return (DDI_PROBE_FAILURE);
		}
	}
#endif
	return (DDI_PROBE_SUCCESS);
}

/*
 * Interface exists: make available by filling in network interface
 * record.  System will initialize the interface when it is ready
 * to accept packets.
 */

static int
psliattach(dip, cmd)
dev_info_t	*dip;
ddi_attach_cmd_t	cmd;
{
	struct psli *pslip;
	int instance;

	if (cmd != DDI_ATTACH)
		return (DDI_FAILURE);

	/*
	 * Allocate soft data structure
	 */

	instance = ddi_get_instance(dip);

	if (ddi_soft_state_zalloc(psli_state, instance) != DDI_SUCCESS) {
		if (pslidebug)
			pslierror(dip, "soft state alloc: failed");
		return (DDI_FAILURE);
	}

	pslip = ddi_get_soft_state(psli_state, instance);

#ifdef	HARDWARE
	/*
	 * XXX Map in the device registers.
	 */
	if (ddi_map_regs(dip, 0, (caddr_t *)&pslip->psli_regsp, 0, 0)) {
		if (pslidebug)
			pslierror(dip, "ddi_map_regs failed");
		goto bad;
	}

	/*
	 * XXX Reject this device if it's a Bus Master in a slave-only slot.
	 */

	if (ddi_slaveonly(dip) == DDI_SUCCESS &&
		(pslip->psli_flags & PSLISLAVE) == 0) {
		if (pslidebug)
			pslierror(dip, "not used - device in slot only slot");
		goto bad;
	}

	/*
	 * check to see if your device interrupts at a high level -
	 * if it does, you can a) print a msg saying you do not support high
	 * level interrupts OR b) you can write a high level intrrupt handler.
	 * To see how to do this read the Solaris 2.x Writing Device Drivers
	 * manual
	 */

	if (ddi_intr_hilevel(dip, 0) != 0) {
		if (pslidebug)
			pslierror(dip,
			    "High level interrupts not supported");
	}

	/*
	 * XXX Add interrupt to system.
	 */
	if (ddi_add_intr(dip, 0, &pslip->iblock_cookie, 0,
				psliintr, (caddr_t)pslip)) {
		if (pslidebug)
			pslierror(dip, "ddi_add_intr failed");
		goto bad;
	}

	/*
	 * Initialize mutex's for this device.
	 */
	mutex_init(&pslip->psli_xmitlock, "psli xmit lock", MUTEX_DRIVER,
	    (void *)pslip->iblock_cookie);
	mutex_init(&pslip->psli_intrlock, "psli intr lock", MUTEX_DRIVER,
	    (void *)pslip->iblock_cookie);

#endif
	/*
	 * Stuff private info into dip.
	 */
	ddi_set_driver_private(dip, (caddr_t)pslip);
	pslip->psli_dip = dip;

	/*
	 * Link this per-device structure in with the rest.
	 */
	pslip->psli_nextp = pslidev;
	pslidev = pslip;

	/*
	 * XXX Get the local medium address and store in pslip->psli_ouraddr.
	 * modify localmedaddr()
	 */

	localmedaddr(pslip->psli_ouraddr);

	/*
	 * Create the filesystem device node.
	 */
	if (ddi_create_minor_node(dip, "psli", S_IFCHR,
		ddi_get_instance(dip), DDI_NT_NET, CLONE_DEV) == DDI_FAILURE) {
		if (pslidebug)
			pslierror(dip, "ddi_create_minor_node failed");
		ddi_remove_minor_node(dip, NULL);
		goto bad;
	}

#ifdef  KSTAT
	pslistatinit(pslip);
#endif  KSTAT

	ddi_report_dev(dip);

	return (DDI_SUCCESS);

bad:
#ifdef	HARDWARE
	if (pslip->psli_regsp)
		ddi_unmap_regs(dip, 0, (caddr_t *)&pslip->psli_regsp, 0, 0);

	/* remove interrupts added to the system */
	if (pslip->iblock_cookie)
		ddi_remove_intr(dip, 0, pslip->iblock_cookie);

	/* destroy all mutex's */
	mutex_destroy(&pslip->psli_xmitlock);
	mutex_destroy(&pslip->psli_intrlock);

#endif
	if (pslip)
		ddi_soft_state_free(psli_state, instance);

	if (pslidebug)
		pslierror(pslip->psli_dip, "attach failed");

	return (DDI_FAILURE);
}

/*
 * Detach - Free resources allocated in attach
 */

/*ARGSUSED*/
static int
pslidetach(dip, cmd)
dev_info_t		*dip;
ddi_detach_cmd_t	cmd;
{
	int instance;
	struct psli *pslip;

	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	instance = ddi_get_instance(dip);

	pslip = ddi_get_soft_state(psli_state, instance);
	if (pslip == NULL)
		return (DDI_FAILURE);

	/* unmap device registers */
	if (pslip->psli_regsp)
		ddi_unmap_regs(dip, 0, (caddr_t *)&pslip->psli_regsp, 0, 0);

	/* remove interrupts added to the system */
	if (pslip->iblock_cookie)
		ddi_remove_intr(dip, 0, pslip->iblock_cookie);

	/* destroy all mutex's */
	mutex_destroy(&pslip->psli_xmitlock);
	mutex_destroy(&pslip->psli_intrlock);

	/* release soft state */
	ddi_soft_state_free(psli_state, instance);

	/* release minor node */
	ddi_remove_minor_node(dip, NULL);

	return (DDI_SUCCESS);

}

/*
 * Translate "dev_t" to a pointer to the associated "dev_info_t".
 */
/* ARGSUSED */
static int
psliinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
	void **result)
{
	dev_t	dev;
	int	instance, rc;
	struct	psli	*pslip;


	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		dev = (dev_t)arg;
		instance = getminor(dev);
		pslip = ddi_get_soft_state(psli_state, instance);
		if (pslip == NULL) {
			*result = NULL;
			rc = DDI_FAILURE;
		} else {
			*result = pslip->psli_dip;
			rc = DDI_SUCCESS;
		}
		break;

	case DDI_INFO_DEVT2INSTANCE:
		dev = (dev_t)arg;
		instance = getminor(dev);
		*result = (void *)instance;
		rc = DDI_SUCCESS;
		break;

	default:
		rc = DDI_FAILURE;
		break;
	}
	return (rc);
}

#ifdef  KSTAT

static int
pslistat_kstat_update(kstat_t *ksp, int rw)
{
	struct  psli		*pslip;
	struct  pslistat	*pslisp;

	if (rw == KSTAT_WRITE)
		return (EACCES);
	pslip = (struct psli *)ksp->ks_private;
	pslisp = (struct pslistat *)ksp->ks_data;
	pslisp->les_ipackets.value.ul = pslip->psli_ipackets;
	pslisp->les_ierrors.value.ul = pslip->psli_ierrors;
	pslisp->les_opackets.value.ul = pslip->psli_opackets;
	pslisp->les_oerrors.value.ul = pslip->psli_oerrors;
	pslisp->les_collisions.value.ul = pslip->psli_collisions;
	return (0);
}

static void
pslistatinit(struct  psli    *pslip)
{
	struct  kstat		*ksp;
	struct  pslistat	*pslisp;

	if ((ksp = kstat_create("psli", ddi_get_instance(pslip->psli_dip),
	NULL, "net", KSTAT_TYPE_NAMED,
	sizeof (struct pslistat) / sizeof (kstat_named_t), 0)) == NULL) {
		pslierror(pslip->psli_dip, "kstat_create failed");
		return;
	}
	pslisp = (struct pslistat *)(ksp->ks_data);
	kstat_named_init(&pslisp->les_ipackets, "ipackets", KSTAT_DATA_ULONG);
	kstat_named_init(&pslisp->les_ierrors, "ierrors", KSTAT_DATA_ULONG);
	kstat_named_init(&pslisp->les_opackets, "opackets", KSTAT_DATA_ULONG);
	kstat_named_init(&pslisp->les_oerrors, "oerrors", KSTAT_DATA_ULONG);
	kstat_named_init(&pslisp->les_collisions, "collisions",
	    KSTAT_DATA_ULONG);
	ksp->ks_update = pslistat_kstat_update;
	ksp->ks_private = (void *)pslip;
	kstat_install(ksp);
}

#endif	KSTAT

/*ARGSUSED*/
static
psliopen(queue_t *rq, dev_t *devp, int flag, int sflag, cred_t *credp)
{
	struct	pslistr	*slp;
	struct	pslistr	**prevslp;
	minor_t	minordev;
	int	rc = 0;

	ASSERT(sflag != MODOPEN);

	/*
	 * Serialize all driver open and closes.
	 */
	rw_enter(&pslistruplock, RW_WRITER);

	/*
	 * Determine minor device number.
	 */
	prevslp = &pslistrup;
	if (sflag == CLONEOPEN) {
		minordev = 0;
		for (; ((slp = *prevslp) != NULL); prevslp = &slp->sl_nextp) {
			if (minordev < slp->sl_minor)
				break;
			minordev++;
		}
		*devp = makedevice(getmajor(*devp), minordev);
	} else
		minordev = getminor(*devp);

	if (rq->q_ptr)
		goto done;

	if ((slp = GETSTRUCT(struct pslistr, 1)) == NULL) {
		rc = ENOMEM;
		rw_exit(&pslistruplock);
		return (rc);
	}

	slp->sl_minor = minordev;
	slp->sl_rq = rq;
	slp->sl_state = DL_UNATTACHED;
	slp->sl_sap = 0;
	slp->sl_flags = 0;
	slp->sl_pslip = NULL;
	slp->sl_mccount = 0;
	slp->sl_mctab = NULL;
	mutex_init(&slp->sl_lock, "psli stream lock", MUTEX_DRIVER, (void *)0);

	/*
	 * Link new entry into the list of active entries.
	 */
	slp->sl_nextp = *prevslp;
	*prevslp = slp;

	rq->q_ptr = WR(rq)->q_ptr = slp;

	/*
	 * Disable our write-side service procedure.
	 */
	noenable(WR(rq));

done:
	rw_exit(&pslistruplock);
	qprocson(rq);
	return (rc);
}

/*ARGSUSED*/
static int
psliclose(queue_t *rq, int flag, cred_t *credp)
{
	struct	pslistr	*slp;
	struct	pslistr	**prevslp;

	ASSERT(rq);
	ASSERT(rq->q_ptr);


	slp = (struct pslistr *)rq->q_ptr;

	/*
	 * Implicit detach Stream from interface.
	 */
	if (slp->sl_pslip)
		pslidodetach(slp);

	qprocsoff(rq);

	rw_enter(&pslistruplock, RW_WRITER);

	/*
	 * Unlink the per-Stream entry from the active list and free it.
	 */
	for (prevslp = &pslistrup; ((slp = *prevslp) != NULL);
	    prevslp = &slp->sl_nextp)
		if (slp == (struct pslistr *)rq->q_ptr)
			break;
	ASSERT(slp);
	*prevslp = slp->sl_nextp;

	mutex_destroy(&slp->sl_lock);
	kmem_free(slp, sizeof (struct pslistr));

	rq->q_ptr = WR(rq)->q_ptr = NULL;

	rw_exit(&pslistruplock);
	return (0);
}

static
psliwput(queue_t *wq, mblk_t *mp)
{
	struct	pslistr	*slp = (struct pslistr *)wq->q_ptr;
	struct	psli	*pslip;

	switch (DB_TYPE(mp)) {
		case M_DATA:
			pslip = slp->sl_pslip;
			if ((slp->sl_flags & SLRAW == 0) ||
			    (slp->sl_state != DL_IDLE) ||
			    (pslip == NULL)) {
				pserror(wq, mp, EPROTO);
				break;
			}

			/*
			 * If any msgs already enqueued or the interface will
			 * loop back up the message (due to PSLIPROMISC), then
			 * enqueue the msg.  Otherwise just xmit it directly.
			 */
			if (wq->q_first) {
				(void) putq(wq, mp);
				pslip->psli_wantw = 1;
			} else if (pslip->psli_flags & PSLIPROMISC) {
				(void) putq(wq, mp);
				qenable(wq);
			} else
				(void) pslistart(wq, mp, pslip);

			break;

		case M_PROTO:
		case M_PCPROTO:
			(void) putq(wq, mp);
			qenable(wq);
			break;

		case M_IOCTL:
			psliioctl(wq, mp);
			break;

		case M_FLUSH:
			if (*mp->b_rptr & FLUSHW) {
				flushq(wq, FLUSHALL);
				*mp->b_rptr &= ~FLUSHW;
			}
			if (*mp->b_rptr & FLUSHR)
				qreply(wq, mp);
			else
				freemsg(mp);
			break;

		default:
			freemsg(mp);
			break;
	}
	return (0);
}

/*
 * Enqueue M_PROTO/M_PCPROTO (always) and M_DATA (sometimes) on the wq.
 *
 * Processing of some of the M_PROTO/M_PCPROTO msgs involves acquiring
 * internal locks that are held across upstream putnext calls.
 * Specifically there's the problem of psliintr() holding psli_intrlock
 * and pslistruplock when it calls putnext() and that thread looping
 * back around to call psliwput and, eventually, psliinit() to create a
 * recursive lock panic.  There are two obvious ways of solving this
 * problem: (1) have psliintr() do putq instead of putnext which provides
 * the loopback "cutout" right at the rq, or (2) allow psliintr() to putnext
 * and put the loopback "cutout" around psliproto().  We choose the latter
 * for performance reasons.
 *
 * M_DATA messages are enqueued on the wq *only* when the xmit side
 * is out of buffer space.  Once the xmit resource is available again,
 * wsrv() is enabled and tries to xmit all the messages on the wq.
 */
static
psliwsrv(queue_t	*wq)
{
	mblk_t	*mp;
	struct	pslistr	*slp;
	struct	psli	*pslip;

	slp = (struct pslistr *)wq->q_ptr;
	pslip = slp->sl_pslip;

	while (mp = getq(wq))
		switch (DB_TYPE(mp)) {
			case	M_DATA:
				    if (pslip) {
					if (pslistart(wq, mp, pslip))
						goto done;
				    } else
					freemsg(mp);
				break;

			case	M_PROTO:
			case	M_PCPROTO:
				psliproto(wq, mp);
				break;

			default: /* nothing is working at ths point */
				ASSERT(0);
				break;
		}

done:
	return (0);
}


static void
psliproto(queue_t	*wq, mblk_t	*mp)
{
	union	DL_primitives	*dlp;
	struct	pslistr	*slp;
	u_long	prim;

	slp = (struct pslistr *)wq->q_ptr;
	dlp = (union DL_primitives *)mp->b_rptr;
	prim = dlp->dl_primitive;

	mutex_enter(&slp->sl_lock);

	switch (prim) {
		case	DL_UNITDATA_REQ:
			psliudreq(wq, mp);
			break;

		case	DL_ATTACH_REQ:
			psliareq(wq, mp);
			break;

		case	DL_DETACH_REQ:
			pslidreq(wq, mp);
			break;

		case	DL_BIND_REQ:
			pslibreq(wq, mp);
			break;

		case	DL_UNBIND_REQ:
			psliubreq(wq, mp);
			break;

		case	DL_INFO_REQ:
			psliireq(wq, mp);
			break;

		case	DL_PROMISCON_REQ:
			psliponreq(wq, mp);
			break;

		case	DL_PROMISCOFF_REQ:
			pslipoffreq(wq, mp);
			break;

		case	DL_ENABMULTI_REQ:
			psliemreq(wq, mp);
			break;

		case	DL_DISABMULTI_REQ:
			pslidmreq(wq, mp);
			break;

		case	DL_PHYS_ADDR_REQ:
			pslipareq(wq, mp);
			break;

		case	DL_SET_PHYS_ADDR_REQ:
			pslispareq(wq, mp);
			break;

		default:
			psdlerrorack(wq, mp, prim, DL_UNSUPPORTED, 0);
			break;
	}

	mutex_exit(&slp->sl_lock);
}

static void
psliioctl(queue_t	*wq, mblk_t	*mp)
{
	struct	iocblk	*iocp = (struct iocblk *)mp->b_rptr;
	struct	pslistr	*slp = (struct pslistr *)wq->q_ptr;

	switch (iocp->ioc_cmd) {
	case DLIOCRAW:		/* raw M_DATA mode */
		slp->sl_flags |= SLRAW;
		psiocack(wq, mp, 0, 0);
		break;

	default:
		psiocnak(wq, mp, 0, EINVAL);
		break;
	}
}

	/*	Generic DLPI interface routines */

static void
psliareq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	struct	psli	*pslip;
	int	ppa;

	slp = (struct pslistr *)wq->q_ptr;
	dlp = (union DL_primitives *)mp->b_rptr;

	if (MBLKL(mp) < DL_ATTACH_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_ATTACH_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state != DL_UNATTACHED) {
		psdlerrorack(wq, mp, DL_ATTACH_REQ, DL_OUTSTATE, 0);
		return;
	}

	ppa = dlp->attach_req.dl_ppa;

	/*
	 * Valid ppa?
	 */
	for (pslip = pslidev; pslip; pslip = pslip->psli_nextp)
		if (ppa == ddi_get_instance(pslip->psli_dip))
			break;
	if (pslip == NULL) {
		psdlerrorack(wq, mp, dlp->dl_primitive, DL_BADPPA, 0);
		return;
	}

	/*
	 * Has device been initialized?  Do so if necessary.
	 */
	if ((pslip->psli_flags & PSLIRUNNING) == 0) {
		if (psliinit(pslip)) {
			psdlerrorack(wq, mp, dlp->dl_primitive,
				DL_INITFAILED, 0);
			return;
		}
	}

	/*
	 * Set link to device and update our state.
	 */
	slp->sl_pslip = pslip;
	slp->sl_state = DL_UNBOUND;

	psdlokack(wq, mp, DL_ATTACH_REQ);
}

static void
pslidreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_DETACH_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_DETACH_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state != DL_UNBOUND) {
		psdlerrorack(wq, mp, DL_DETACH_REQ, DL_OUTSTATE, 0);
		return;
	}

	pslidodetach(slp);
	psdlokack(wq, mp, DL_DETACH_REQ);
}

/*
 * Detach a Stream from an interface.
 */
static void
pslidodetach(struct	pslistr	*slp)
{
	struct	pslistr	*tslp;
	struct	psli	*pslip;
	int	reinit = 0;

	ASSERT(slp->sl_pslip);

	pslip = slp->sl_pslip;
	slp->sl_pslip = NULL;

	/*
	 * Disable promiscuous mode if on.
	 */
	if (slp->sl_flags & SLALLPHYS) {
		slp->sl_flags &= ~SLALLPHYS;
		reinit = 1;
	}

	/*
	 * Disable ALLMULTI mode if on.
	 */
	if (slp->sl_flags & SLALLMULTI) {
		slp->sl_flags &= ~SLALLMULTI;
		reinit = 1;
	}

	/*
	 * Disable any Multicast Addresses.
	 */
	slp->sl_mccount = 0;
	if (slp->sl_mctab) {
		kmem_free(slp->sl_mctab, DEVMCALLOC);
		slp->sl_mctab = NULL;
		reinit = 1;
	}

	/*
	 * Detach from device structure.
	 * Uninit the device when no other streams are attached to it.
	 */
	for (tslp = pslistrup; tslp; tslp = tslp->sl_nextp)
		if (tslp->sl_pslip == pslip)
			break;
	if (tslp == NULL)
		psliuninit(pslip);
	else if (reinit)
		(void) psliinit(pslip);

	slp->sl_state = DL_UNATTACHED;
}

static void
pslibreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	struct	psli	*pslip;
	struct	pslidladdr	psliaddr;
	u_long	sap;
	int	xidtest;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_BIND_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_BIND_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state != DL_UNBOUND) {
		psdlerrorack(wq, mp, DL_BIND_REQ, DL_OUTSTATE, 0);
		return;
	}

	dlp = (union DL_primitives *)mp->b_rptr;
	pslip = slp->sl_pslip;
	sap = dlp->bind_req.dl_sap;
	xidtest = dlp->bind_req.dl_xidtest_flg;

	ASSERT(pslip);

	if (xidtest) {
		psdlerrorack(wq, mp, DL_BIND_REQ, DL_NOAUTO, 0);
		return;
	}

	if (sap > MEDIUMTYPE_MAX) {
		psdlerrorack(wq, mp, dlp->dl_primitive, DL_BADSAP, 0);
		return;
	}

	/*
	 * Save SAP value for this Stream and change state.
	 */
	slp->sl_sap = sap;
	slp->sl_state = DL_IDLE;

	psliaddr.dl_sap = sap;
	bcopy((caddr_t)&pslip->psli_ouraddr, (caddr_t)&psliaddr.dl_phys,
	sizeof (struct medium_addr));
	psdlbindack(wq, mp, sap, (caddr_t)&psliaddr, DEVICE_ADDRL, 0, 0);
}

static void
psliubreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_UNBIND_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_UNBIND_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state != DL_IDLE) {
		psdlerrorack(wq, mp, DL_UNBIND_REQ, DL_OUTSTATE, 0);
		return;
	}

	slp->sl_state = DL_UNBOUND;

	(void) putnextctl1(RD(wq), M_FLUSH, FLUSHRW);

	psdlokack(wq, mp, DL_UNBIND_REQ);
}

static void
psliireq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	dl_info_ack_t	*dlip;
	struct	pslidladdr	*dlap;
	struct	medium_addr	*ep;
	int	size;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_INFO_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_INFO_REQ, DL_BADPRIM, 0);
		return;
	}

	/*
	 * Exchange current msg for a DL_INFO_ACK.
	 */
	size = sizeof (dl_info_ack_t) + DEVICE_ADDRL + MEDIUM_ADDRL;
	if ((mp = psexchange(wq, mp, size, M_PCPROTO, DL_INFO_ACK)) == NULL)
		return;

	/*
	 * Fill in the DL_INFO_ACK fields and reply.
	 */
	dlip = (dl_info_ack_t *)mp->b_rptr;
	*dlip = psliinfoack;
	dlip->dl_current_state = slp->sl_state;
	dlap = (struct pslidladdr *)(mp->b_rptr + dlip->dl_addr_offset);
	dlap->dl_sap = slp->sl_sap;
	if (slp->sl_pslip) {
	bcopy((caddr_t)&slp->sl_pslip->psli_ouraddr, (caddr_t)&dlap->dl_phys,
		sizeof (struct medium_addr));
	} else {
		bzero((caddr_t)&dlap->dl_phys, MEDIUM_ADDRL);
	}
	ep = (struct medium_addr *)(mp->b_rptr + dlip->dl_brdcst_addr_offset);
	bcopy((caddr_t)&medbroadcastaddr, (caddr_t)ep,
		sizeof (struct medium_addr));

	qreply(wq, mp);
}

static void
psliponreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_PROMISCON_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_PROMISCON_REQ, DL_BADPRIM, 0);
		return;
	}

	switch (((dl_promiscon_req_t *)mp->b_rptr)->dl_level) {
		case DL_PROMISC_PHYS:
			slp->sl_flags |= SLALLPHYS;
			break;

		case DL_PROMISC_SAP:
			slp->sl_flags |= SLALLSAP;
			break;

		case DL_PROMISC_MULTI:
			slp->sl_flags |= SLALLMULTI;
			break;

		default:
			psdlerrorack(wq, mp, DL_PROMISCON_REQ,
				DL_NOTSUPPORTED, 0);
			return;
	}

	if (slp->sl_pslip)
		(void) psliinit(slp->sl_pslip);

	psdlokack(wq, mp, DL_PROMISCON_REQ);
}

static void
pslipoffreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	int	flag;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_PROMISCOFF_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_PROMISCOFF_REQ, DL_BADPRIM, 0);
		return;
	}

	switch (((dl_promiscoff_req_t *)mp->b_rptr)->dl_level) {
		case DL_PROMISC_PHYS:
			flag = SLALLPHYS;
			break;

		case DL_PROMISC_SAP:
			flag = SLALLSAP;
			break;

		case DL_PROMISC_MULTI:
			flag = SLALLMULTI;
			break;

		default:
			psdlerrorack(wq, mp, DL_PROMISCOFF_REQ,
				DL_NOTSUPPORTED, 0);
			return;
	}

	if ((slp->sl_flags & flag) == 0) {
		psdlerrorack(wq, mp, DL_PROMISCOFF_REQ, DL_NOTENAB, 0);
		return;
	}

	slp->sl_flags &= ~flag;
	if (slp->sl_pslip)
		(void) psliinit(slp->sl_pslip);

	psdlokack(wq, mp, DL_PROMISCOFF_REQ);
}

static void
psliemreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	struct	medium_addr	*addrp;
	int	off;
	int	len;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_ENABMULTI_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_ENABMULTI_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state == DL_UNATTACHED) {
		psdlerrorack(wq, mp, DL_ENABMULTI_REQ, DL_OUTSTATE, 0);
		return;
	}

	dlp = (union DL_primitives *)mp->b_rptr;
	len = dlp->enabmulti_req.dl_addr_length;
	off = dlp->enabmulti_req.dl_addr_offset;
	addrp = (struct medium_addr *)(mp->b_rptr + off);

	if ((len != MEDIUM_ADDRL) ||
		!MBLKIN(mp, off, len)) {
		psdlerrorack(wq, mp, DL_ENABMULTI_REQ, DL_BADADDR, 0);
		return;
	}

	if ((slp->sl_mccount + 1) >= DEVMAXMC) {
		psdlerrorack(wq, mp, DL_ENABMULTI_REQ, DL_TOOMANY, 0);
		return;
	}

	/*
	 * Allocate table on first request.
	 */
	if (slp->sl_mctab == NULL)
		if ((slp->sl_mctab = (struct medium_addr *)
			kmem_alloc(DEVMCALLOC, KM_NOSLEEP)) == NULL) {
			psdlerrorack(wq, mp, DL_ENABMULTI_REQ,
				DL_SYSERR, ENOMEM);
			return;
			}

	slp->sl_mctab[slp->sl_mccount++] = *addrp;

	(void) psliinit(slp->sl_pslip);
	psdlokack(wq, mp, DL_ENABMULTI_REQ);
}

static void
pslidmreq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	struct	medium_addr	*addrp;
	int	off;
	int	len;
	int	i;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_DISABMULTI_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_DISABMULTI_REQ, DL_BADPRIM, 0);
		return;
	}

	if (slp->sl_state == DL_UNATTACHED) {
		psdlerrorack(wq, mp, DL_DISABMULTI_REQ, DL_OUTSTATE, 0);
		return;
	}

	dlp = (union DL_primitives *)mp->b_rptr;
	len = dlp->disabmulti_req.dl_addr_length;
	off = dlp->disabmulti_req.dl_addr_offset;
	addrp = (struct medium_addr *)(mp->b_rptr + off);

	if ((len != MEDIUM_ADDRL) || !MBLKIN(mp, off, len)) {
		psdlerrorack(wq, mp, DL_DISABMULTI_REQ, DL_BADADDR, 0);
		return;
	}

	/*
	 * Find the address in the multicast table for this Stream
	 * and delete it by shifting all subsequent multicast
	 * table entries over one.
	 */
	for (i = 0; i < slp->sl_mccount; i++)
		/* all this is architecture specific */
		if (bcmp((caddr_t)addrp, (caddr_t)&slp->sl_mctab[i],
			sizeof (struct medium_addr)) == 0) {
			bcopy((caddr_t)&slp->sl_mctab[i+1],
				(caddr_t)&slp->sl_mctab[i],
				((slp->sl_mccount - i) *
				sizeof (struct medium_addr)));
			slp->sl_mccount--;
			(void) psliinit(slp->sl_pslip);
			psdlokack(wq, mp, DL_DISABMULTI_REQ);
			break;
		}
}

static void
pslipareq(queue_t	*wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	int	type;
	struct	psli	*pslip;
	struct	medium_addr	addr;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_PHYS_ADDR_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_PHYS_ADDR_REQ, DL_BADPRIM, 0);
		return;
	}

	dlp = (union DL_primitives *)mp->b_rptr;
	type = dlp->physaddr_req.dl_addr_type;
	pslip = slp->sl_pslip;

	if (pslip == NULL) {
		psdlerrorack(wq, mp, DL_PHYS_ADDR_REQ, DL_OUTSTATE, 0);
		return;
	}

	switch (type) {
		case	DL_FACT_PHYS_ADDR:
			(void) localmedaddr(&addr);
			break;

		case	DL_CURR_PHYS_ADDR:
			bcopy((caddr_t)&pslip->psli_ouraddr, (caddr_t)&addr,
				sizeof (struct medium_addr));
			break;

		default:
			psdlerrorack(wq, mp, DL_PHYS_ADDR_REQ,
				DL_NOTSUPPORTED, 0);
			return;
	}

	psdlphysaddrack(wq, mp, (caddr_t)&addr, MEDIUM_ADDRL);
}

/*
 * XXX figure out your local medium address
 */

static void
localmedaddr(struct medium_addr *addr)
{
	(void) strcpy((char *)addr->medaddr, "Physical Medium Address");
}



static void
pslispareq(queue_t *wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	union	DL_primitives	*dlp;
	int	off;
	int	len;
	struct	medium_addr	*addrp;
	struct	psli	*pslip;

	slp = (struct pslistr *)wq->q_ptr;

	if (MBLKL(mp) < DL_SET_PHYS_ADDR_REQ_SIZE) {
		psdlerrorack(wq, mp, DL_SET_PHYS_ADDR_REQ, DL_BADPRIM, 0);
		return;
	}

	dlp = (union DL_primitives *)mp->b_rptr;
	len = dlp->set_physaddr_req.dl_addr_length;
	off = dlp->set_physaddr_req.dl_addr_offset;

	if (!MBLKIN(mp, off, len)) {
		psdlerrorack(wq, mp, DL_SET_PHYS_ADDR_REQ, DL_BADPRIM, 0);
		return;
	}

	addrp = (struct medium_addr *)(mp->b_rptr + off);

	/*
	 * XXX if length of address isn't right or the address
	 * specified is a multicast or broadcast address.
	 * then return error.
	 */

	/*
	 * Error if this stream is not attached to a device.
	 */
	if ((pslip = slp->sl_pslip) == NULL) {
		psdlerrorack(wq, mp, DL_SET_PHYS_ADDR_REQ, DL_OUTSTATE, 0);
		return;
	}

	/*
	 * Set new interface local address and re-init device.
	 * This is destructive to any other streams attached
	* to this device.
	 */
	bcopy((caddr_t)addrp, (caddr_t)&pslip->psli_ouraddr,
		sizeof (struct medium_addr));
	(void) psliinit(slp->sl_pslip);

	psdlokack(wq, mp, DL_SET_PHYS_ADDR_REQ);
}

static void
psliudreq(queue_t *wq, mblk_t	*mp)
{
	struct	pslistr	*slp;
	register	struct	psli	*pslip;
	register	dl_unitdata_req_t	*dludp;
	mblk_t	*nmp;
	struct	pslidladdr	*dlap;
	struct	medium_header *headerp;
	int	off, len;

	slp = (struct pslistr *)wq->q_ptr;
	pslip = slp->sl_pslip;

	dludp = (dl_unitdata_req_t *)mp->b_rptr;

	off = dludp->dl_dest_addr_offset;
	len = dludp->dl_dest_addr_length;

	/*
	 * Validate destination address format.
	 */
	if (!MBLKIN(mp, off, len) || (len != MEDIUM_ADDRL)) {
		psdluderrorind(wq, mp, (u_char *)(mp->b_rptr + off), len,
			DL_BADADDR, 0);
		return;
	}

	/*
	 * Error if no M_DATA follows.
	 */
	nmp = mp->b_cont;
	if (nmp == NULL) {
		psdluderrorind(wq, mp, (u_char *)(mp->b_rptr + off), len,
			DL_BADDATA, 0);
		return;
	}

	dlap = (struct pslidladdr *)(mp->b_rptr + off);

	/*
	 * Create medium header by either prepending it onto the
	 * next mblk if possible, or reusing the M_PROTO block if not.
	 */
	if ((DB_REF(nmp) == 1) &&
		(MBLKHEAD(nmp) >= sizeof (struct medium_header)) &&
		(((ulong)nmp->b_rptr & 0x1) == 0)) {
		nmp->b_rptr -= sizeof (struct medium_header);
		headerp = (struct medium_header *)nmp->b_rptr;
		bcopy((caddr_t)&dlap->dl_phys, (caddr_t)&headerp->med_dhost,
			sizeof (struct medium_addr));
		bcopy((caddr_t)&pslip->psli_ouraddr,
			(caddr_t)&headerp->med_shost,
			sizeof (struct medium_addr));
		headerp->med_type = dlap->dl_sap;
		freeb(mp);
		mp = nmp;
	} else {
		DB_TYPE(mp) = M_DATA;
		headerp = (struct medium_header *)mp->b_rptr;
		mp->b_wptr = mp->b_rptr + sizeof (struct medium_header);
		bcopy((caddr_t)&dlap->dl_phys, (caddr_t)&headerp->med_dhost,
			sizeof (struct medium_addr));
		bcopy((caddr_t)&pslip->psli_ouraddr,
			(caddr_t)&headerp->med_shost,
			sizeof (struct medium_addr));
		headerp->med_type = dlap->dl_sap;
	}

	/*
	 * Transmit it.
	 */
	(void) pslistart(wq, mp, pslip);
}

/*
 * XXX Start transmission.
 * Return zero on success,
 * otherwise put msg on wq, set 'want' flag and return nonzero.
 */
/*ARGSUSED*/
static
pslistart(queue_t *wq, mblk_t *mp, struct psli	*pslip)
{

	/* Start transmitting */
	return (1);
}

#ifdef	HARDWARE
static u_int
psliintr(struct psli *pslip)
{
	caddr_t 	rmdp = 0;

	mutex_enter(&pslip->psli_intrlock);


	/* XXX read interrupt status register */


	/*
	 * XXX Check if interrupt from my device
	 * if not, return DDI_INTR_UNCLAIMED
	 */


	/* XXX Clear interrupt status */


	/*
	 * XXX Check for receive activity.
	 */

	/* XXX if it is a receive intr */

		/*
		 * XXX Sync the receive descriptors before looking at them
		 * use ddi_dma_sync() with the DDI_DMA_SYNC_FORCPU flag
		 */

		/*
		 * XXX Now loop through each receive descriptor
		 */

		while (rmdp) {

			/* XXX call psliread() to get the data */
			psliread(pslip, rmdp);

			/* XXX Increment to next receive descriptor */

			/*
			 * XXX sync the next receive descriptor before using it
			 */
		}

	/*
	 * XXX Check for transmit activity.
	 */

		/* XXX if it is a transmit activity */

		pslireclaim(pslip);

	/*
	 * Check for errors.
	 */

	mutex_exit(&pslip->psli_intrlock);

	return (DDI_INTR_CLAIMED);
}
#endif

/*
 * XXX Transmit completion reclaiming.
 */

static void
pslireclaim(struct psli	*pslip)
{

	/*
	 * XXX sync the transmit descriptor before looking at it.
	 * use ddi_dma_sync() with DDI_DMA_SYNC_FORCPU flag
	 */


	/* XXX Loop through each transmit descriptor */

	/*
	* Check for any msgs that were queued
	* due to out-of-descriptors condition
	*/

	if (pslip->psli_wantw)
		psliwenable(pslip);
}


/*
 * Start xmit on any msgs previously enqueued on any write queues.
 */
static void
psliwenable(struct	psli	*pslip)
{
	struct	pslistr	*slp;
	queue_t	*wq;

	/*
	 * Order of wantw accesses is important.
	 */
	do {
		pslip->psli_wantw = 0;
		for (slp = pslistrup; slp; slp = slp->sl_nextp)
			if ((wq = WR(slp->sl_rq))->q_first)
				qenable(wq);
	} while (pslip->psli_wantw);
}

/*
 * XXX Test upstream destination sap and address match.
 */
static struct pslistr *
psliaccept(struct	pslistr	*slp, struct	psli	*pslip,
int	type, struct	medium_addr	*addrp)
{
	int	sap;
	int	flags;

	for (; slp; slp = slp->sl_nextp) {
		sap = slp->sl_sap;
		flags = slp->sl_flags;

		if ((slp->sl_pslip == pslip) && PSLISAPMATCH(sap, type, flags))
			/* bcmp and family are architecture specific */
			if ((bcmp((caddr_t)addrp, (caddr_t)&pslip->psli_ouraddr,
				sizeof (struct medium_addr)) == 0) ||
			(bcmp((caddr_t)addrp, (caddr_t)&medbroadcastaddr,
				sizeof (struct medium_addr)) == 0) ||
			(flags & SLALLPHYS) ||
			pslimcmatch((struct pslistr *)slp,
				(struct medium_addr *)addrp))
			return (slp);
	}

	return (NULL);
}

/*
 * XXX psliread() gets called by psliintr()
 */

/*ARGSUSED*/
static void
psliread(struct psli	*pslip, caddr_t 	rmdp)
{

	mblk_t	*mp = 0;

	/*
	 * XXX Check for packet errors.
	 */

	/*
	 * XXX Sync the received buffer before looking at it.
	 * Use ddi_dma_sync() with DDI_DMA_SYNC_FORKERNEL flag
	 */

	/*
	 * XXX Allocate replacement buffer
	 */

	pslisendup(pslip, mp, psliaccept);

}

/*
 * XXX Send packet upstream.
 * Assume mp->b_rptr points to medium_header.
 */
static void
pslisendup(struct	psli	*pslip, mblk_t	*mp,
struct	pslistr	*(*acceptfunc)())
{
	int	type;
	struct	medium_addr	*dhostp, *shostp;
	struct	pslistr	*slp, *nslp;
	mblk_t	*nmp;
	ulong 	isgroupaddr;

	dhostp = &((struct medium_header *)mp->b_rptr)->med_dhost;
	shostp = &((struct medium_header *)mp->b_rptr)->med_shost;
	type = ((struct medium_header *)mp->b_rptr)->med_type;

	isgroupaddr = 555; /* XXX Fill in a appropriate group address */

	/*
	 * While holding a reader lock on the linked list of streams structures,
	 * attempt to match the address criteria for each stream
	 * and pass up the  DL_UNITDATA_IND.
	 */

	rw_enter(&pslistruplock, RW_READER);

	if ((slp = (*acceptfunc)(pslistrup, pslip, type, dhostp)) == NULL) {
		rw_exit(&pslistruplock);
		freemsg(mp);
		return;
	}

	/*
	 * Loop on matching open streams until (*acceptfunc)() returns NULL.
	 */
	for (; nslp = (*acceptfunc)(slp->sl_nextp, pslip, type, dhostp);
		slp = nslp)
		if (canputnext((queue_t *)slp->sl_rq->q_next)) {
			if (nmp = dupmsg(mp)) {
				if (!isgroupaddr) {
					nmp->b_rptr +=
						sizeof (struct medium_header);
					(void) putq(slp->sl_rq, nmp);
				} else if (slp->sl_flags & SLRAW)
					(void) putq(slp->sl_rq, nmp);
				else if ((nmp = psliaddudind(pslip, nmp, shostp,
						dhostp, type, isgroupaddr)))
						(void) putq(slp->sl_rq, nmp);
			}
		}


	/*
	 * Do the last one.
	 */
	if (canputnext((queue_t *)slp->sl_rq->q_next)) {
		if (!isgroupaddr) {
			mp->b_rptr += sizeof (struct medium_header);
			(void) putq(slp->sl_rq, mp);
		} else if (slp->sl_flags & SLRAW)
			(void) putq(slp->sl_rq, mp);
		else if ((mp = psliaddudind(pslip, mp, shostp, dhostp,
			type, isgroupaddr)))
			(void) putq(slp->sl_rq, mp);
	} else {
		freemsg(mp);
	}

	rw_exit(&pslistruplock);
}

/*
 * Prefix msg with a DL_UNITDATA_IND mblk and return the new msg.
 */
static mblk_t *
psliaddudind(struct	psli	*pslip, mblk_t	*mp,
struct	medium_addr	*shostp, struct	medium_addr	*dhostp,
int	type, ulong	isgroupaddr)
{
	dl_unitdata_ind_t	*dludindp;
	struct	pslidladdr	*dlap;
	mblk_t	*nmp;
	int	size;

	mp->b_rptr += sizeof (struct medium_header);

	/*
	 * Allocate an M_PROTO mblk for the DL_UNITDATA_IND.
	 */
	size = sizeof (dl_unitdata_ind_t) + DEVICE_ADDRL + DEVICE_ADDRL;
	if ((nmp = allocb(PSLIHEADROOM + size, BPRI_LO)) == NULL) {
		pslip->psli_ierrors++;
		if (pslidebug)
			pslierror(pslip->psli_dip, "allocb failed");
		freemsg(mp);
		return (NULL);
	}
	DB_TYPE(nmp) = M_PROTO;
	nmp->b_wptr = nmp->b_datap->db_lim;
	nmp->b_rptr = nmp->b_wptr - size;

	/*
	 * Construct a DL_UNITDATA_IND primitive.
	 */
	dludindp = (dl_unitdata_ind_t *)nmp->b_rptr;
	dludindp->dl_primitive = DL_UNITDATA_IND;
	dludindp->dl_dest_addr_length = DEVICE_ADDRL;
	dludindp->dl_dest_addr_offset = sizeof (dl_unitdata_ind_t);
	dludindp->dl_src_addr_length = DEVICE_ADDRL;
	dludindp->dl_src_addr_offset = sizeof (dl_unitdata_ind_t) +
								DEVICE_ADDRL;
	dludindp->dl_group_address = isgroupaddr;

	dlap = (struct pslidladdr *)(nmp->b_rptr + sizeof (dl_unitdata_ind_t));
	bcopy((caddr_t)dhostp, (caddr_t)&dlap->dl_phys,
		sizeof (struct medium_addr));
	dlap->dl_sap = (u_short)type;

	dlap = (struct pslidladdr *)(nmp->b_rptr + sizeof (dl_unitdata_ind_t)
					+ DEVICE_ADDRL);
	bcopy((caddr_t)shostp, (caddr_t)&dlap->dl_phys,
		sizeof (struct medium_addr));
	dlap->dl_sap = (u_short)type;

	/*
	 * Link the M_PROTO and M_DATA together.
	 */
	nmp->b_cont = mp;
	return (nmp);
}

/*
 * Return TRUE if the given multicast address is one
 * of those that this particular Stream is interested in.
 */
static
pslimcmatch(struct	pslistr	*slp, struct	medium_addr	*addrp)
{
	struct	medium_addr	*mctab;
	int	mccount;
	int	i;

	/*
	 * XXX Return FALSE if not a multicast address.
	 */

	/*
	 * Check if all multicasts have been enable for this Stream
	 */
	if (slp->sl_flags & SLALLMULTI)
		return (1);

	/*
	 * Return FALSE if no multicast addresses enable for this Stream.
	 */
	if (slp->sl_mccount == 0)
		return (0);

	/*
	 * Otherwise, find it in the table.
	 */

	mccount = slp->sl_mccount;
	mctab = slp->sl_mctab;

	for (i = 0; i < mccount; i++)
		if (bcmp((caddr_t)addrp, (caddr_t)&mctab[i],
			sizeof (struct medium_addr)) != 0)
			return (1);

	return (0);
}

/*
 * XXX Initialize chip and driver.
 * Return 0 on success, nonzero on error.
 */
static int
psliinit(struct	psli	*pslip)
{

	mutex_enter(&pslip->psli_intrlock);
	mutex_enter(&pslip->psli_xmitlock);
	rw_enter(&pslistruplock, RW_WRITER);

	pslip->psli_flags = 0;
	pslip->psli_wantw = 0;

	/*
	 * XXX Stop the chip.
	 */


	/*
	 * XXX Set device-specific information here
	 */


	/*
	 * XXX Allocate data structures.
	 */


	/*
	 * XXX Free any pending buffers or mgs.
	 */

	/*
	 * XXX Tell the chip it's medium address and the set of multicast
	 * address it should recognize.
	 */

	/*
	 * XXX Chip init.
	 */

	/*
	 * XXX Allow time for the chip to complete initialization.
	 */

	/*
	 * XXX Chip start.
	 */

	pslip->psli_flags |= PSLIRUNNING;

	psliwenable(pslip);

done:
	rw_exit(&pslistruplock);
	mutex_exit(&pslip->psli_xmitlock);
	mutex_exit(&pslip->psli_intrlock);

	return (!(pslip->psli_flags & PSLIRUNNING));
}

/*
 * XXX Un-initialize (STOP) DEVICE
 */
static void
psliuninit(struct psli *pslip)
{
	/*
	 * XXX Allow time  for pending xmit's to complete.
	 */

	/*
	*	Grab mutex's
	*/

	mutex_enter(&pslip->psli_intrlock);
	mutex_enter(&pslip->psli_xmitlock);

	pslip->psli_flags &= ~PSLIRUNNING;

	/*
	 * XXX Stop the chip.
	 */

	/*
	 * Release mutex's
	 */

	mutex_exit(&pslip->psli_xmitlock);
	mutex_exit(&pslip->psli_intrlock);
}

/*ARGSUSED*/
static void
pslierror(dev_info_t	*dip, const char	*fmt, ...)
{

	char name[16];
	char buf[256];
	va_list ap;

	if (dip) {
		(void) sprintf(name, "%s%d", ddi_get_name(dip),
			ddi_get_instance(dip));
	} else {
		(void) sprintf(name, "psli");
	}

	va_start(ap, fmt);
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);

	cmn_err(CE_CONT, "%s:\t%s", name, buf);

}



/*
 *  Some STREAMS routines.
 *
 */


static void
pserror(queue_t	*wq, mblk_t	*mp, int	error)
{
	if ((mp = (mblk_t *)psexchange(wq, mp, 1, M_ERROR, -1)) == NULL)
		return;
	*mp->b_rptr = (unsigned char) error;
	qreply(wq, mp);
}

/*
 * Convert an M_IOCTL into an M_IOCACK.
 * Assumption:  mp points to an M_IOCTL msg.
 */
static void
psiocack(queue_t	*wq, mblk_t	*mp, int	count, int	error)
{
	struct	iocblk	*iocp;

	mp->b_datap->db_type = M_IOCACK;
	iocp = (struct iocblk *)mp->b_rptr;
	iocp->ioc_count = count;
	iocp->ioc_error = error;
	qreply(wq, mp);
}

/*
 * Convert an M_IOCTL into an M_IOCNAK.
 * Assumption:  mp points to an M_IOCTL msg.
 */
static void
psiocnak(queue_t	*wq, mblk_t	*mp, int	count, int	error)
{
	struct	iocblk	*iocp;

	mp->b_datap->db_type = M_IOCNAK;
	iocp = (struct iocblk *)mp->b_rptr;
	iocp->ioc_count = count;
	iocp->ioc_error = error;
	qreply(wq, mp);
}

/*
 * Exchange one msg for another.  Free old msg and allocate
 * a new one if either (1) mp is NULL, (2), requested size
 * is larger than current size, or (3) reference count of
 * the current msg is greater than one.
 * Set db_type and b_rptr/b_wptr appropriately.
 * Set the first longword of the msg to 'primtype' if
 * 'primtype' is not -1.
 *
 * On allocb() failure, return NULL after sending an
 * M_ERROR msg w/ENOSR error upstream.
 */
static mblk_t *
psexchange(queue_t	*wq, mblk_t	*mp, int	size,
unsigned char	type, long	primtype)
{
	if ((mp == NULL) || (MBLKSIZE(mp) < size) || (DB_REF(mp) > 1)) {
		freemsg(mp);
		if ((mp = allocb(size, BPRI_LO)) == NULL) {
			if (mp = allocb(1, BPRI_HI))
				pserror(wq, mp, ENOSR);
			return (NULL);
		}
	}
	mp->b_datap->db_type = type;
	mp->b_rptr = mp->b_datap->db_base;
	mp->b_wptr = mp->b_rptr + size;
	if (primtype >= 0)
		*(long *)mp->b_rptr = primtype;
	return (mp);
}


/*
 *  Common DLPI routines.
 */


static void
psdlbindack(queue_t	*wq, mblk_t	*mp, u_long	sap, caddr_t	addrp,
int	addrlen, int	maxconind, u_long	xidtest)
{
	union	DL_primitives	*dlp;
	int	size;

	size = sizeof (dl_bind_ack_t) + addrlen;
	if ((mp = psexchange(wq, mp, size, M_PCPROTO, DL_BIND_ACK))
		== NULL)
		return;

	dlp = (union DL_primitives *)mp->b_rptr;
	dlp->bind_ack.dl_sap = sap;
	dlp->bind_ack.dl_addr_length = addrlen;
	dlp->bind_ack.dl_addr_offset = sizeof (dl_bind_ack_t);
	dlp->bind_ack.dl_max_conind = maxconind;
	dlp->bind_ack.dl_xidtest_flg = xidtest;
	bcopy((caddr_t)addrp,
		(caddr_t)(mp->b_rptr + sizeof (dl_bind_ack_t)),
		addrlen);

	qreply(wq, mp);
}

static void
psdlokack(queue_t	*wq, mblk_t	*mp, u_long	correct_primitive)
{
	union	DL_primitives	*dlp;

	if ((mp = psexchange(wq, mp, sizeof (dl_ok_ack_t), M_PCPROTO,
			DL_OK_ACK)) == NULL)
		return;
	dlp = (union DL_primitives *)mp->b_rptr;
	dlp->ok_ack.dl_correct_primitive = correct_primitive;
	qreply(wq, mp);
}

static void
psdlerrorack(queue_t	*wq, mblk_t	*mp, u_long	error_primitive,
u_long	errno, u_long	unix_errno)
{
	register	union	DL_primitives	*dlp;

	if ((mp = psexchange(wq, mp, sizeof (dl_error_ack_t), M_PCPROTO,
		DL_ERROR_ACK)) == NULL)
		return;
	dlp = (union DL_primitives *)mp->b_rptr;
	dlp->error_ack.dl_error_primitive = error_primitive;
	dlp->error_ack.dl_errno = errno;
	dlp->error_ack.dl_unix_errno = unix_errno;
	qreply(wq, mp);
}

static void
psdluderrorind(queue_t	*wq, mblk_t	*mp, u_char	*addrp,
u_long	addrlen, u_long	errno, u_long	unix_errno)
{
	union	DL_primitives	*dlp;
	char	buf[DLADDRL];
	int	size;

	if (addrlen > DLADDRL)
		addrlen = DLADDRL;

	bcopy((caddr_t)addrp, (caddr_t)buf, addrlen);

	size = sizeof (dl_uderror_ind_t) + addrlen;

	if ((mp = psexchange(wq, mp, size, M_PCPROTO, DL_UDERROR_IND)) == NULL)
		return;

	dlp = (union DL_primitives *)mp->b_rptr;
	dlp->uderror_ind.dl_dest_addr_length = addrlen;
	dlp->uderror_ind.dl_dest_addr_offset = sizeof (dl_uderror_ind_t);
	dlp->uderror_ind.dl_unix_errno = unix_errno;
	dlp->uderror_ind.dl_errno = errno;
	bcopy((caddr_t)buf,
		(caddr_t)(mp->b_rptr + sizeof (dl_uderror_ind_t)),
		addrlen);
	qreply(wq, mp);
}

static void
psdlphysaddrack(queue_t	*wq, mblk_t	*mp, caddr_t	addrp, int	len)
{
	union	DL_primitives	*dlp;
	int	size;

	size = sizeof (dl_phys_addr_ack_t) + len;
	if ((mp = psexchange(wq, mp, size, M_PCPROTO, DL_PHYS_ADDR_ACK)) ==
									NULL)
		return;
	dlp = (union DL_primitives *)mp->b_rptr;
	dlp->physaddr_ack.dl_addr_length = len;
	dlp->physaddr_ack.dl_addr_offset = sizeof (dl_phys_addr_ack_t);
	bcopy(addrp, (caddr_t)(mp->b_rptr + sizeof (dl_phys_addr_ack_t)), len);
	qreply(wq, mp);
}
