OpenSolaris_b135/uts/common/io/llc1.c

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

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * llc1 - an LLC Class 1 MUX compatible with SunConnect LLC2 uses DLPI
 * interface.  Its primary use is to support RPL for network boot but can be
 * used by other protocols.
 */

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/mkdev.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/devops.h>
#include <sys/sunddi.h>
#include <sys/ksynch.h>
#include <sys/dlpi.h>
#include <sys/ethernet.h>
#include <sys/strsun.h>
#include <sys/stat.h>
#include <netinet/in.h> /* for byteorder macros on machines that define them */
#include <sys/llc1.h>
#include <sys/kstat.h>
#include <sys/debug.h>

/*
 * function prototypes, etc.
 */
static int llc1_open(queue_t *q, dev_t *dev, int flag, int sflag,
	cred_t *cred);
static int llc1_close(queue_t *q, int flag, cred_t *cred);
static int llc1_uwput(queue_t *q, mblk_t *mp);
static int llc1_uwsrv(queue_t *q);
static int llc1_lrsrv(queue_t *q);
static int llc1_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int llc1_detach(dev_info_t *dev, ddi_detach_cmd_t cmd);
static int llc1_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd);

static mblk_t *llc1_form_udata(llc1_t *lld, llc_mac_info_t *macinfo,
	mblk_t *mp);
static mblk_t *llc1_xid_reply(llc_mac_info_t *macinfo, mblk_t *mp, int sap);
static mblk_t *llc1_xid_ind_con(llc1_t *lld, llc_mac_info_t *macinfo,
	mblk_t *mp);
static mblk_t *llc1_test_reply(llc_mac_info_t *macinfo, mblk_t *mp, int sap);
static mblk_t *llc1_test_ind_con(llc1_t *lld, llc_mac_info_t *macinfo,
	mblk_t *mp);

static void llc1_ioctl(queue_t *q, mblk_t *mp);
static void llc1_recv(llc_mac_info_t *macinfo, mblk_t *mp);
static void llc1_req_raw(llc_mac_info_t *macinfo);
static void llc1_find_waiting(llc_mac_info_t *macinfo, mblk_t *mp, long prim);

static minor_t llc1_findminor(llc1dev_t *device);
static void llc1_send_disable_multi(llc_mac_info_t *, llc_mcast_t *);

static void llc1insque(void *elem, void *pred);
static void llc1remque(void *arg);
static void llc1error();
static int llc1_subs_unbind(void);
static void llc1_init_kstat(llc_mac_info_t *macinfo);
static void llc1_uninit_kstat(llc_mac_info_t *macinfo);
static int llc1_update_kstat(kstat_t *ksp, int rw);
static int llc1_broadcast(struct ether_addr *addr, llc_mac_info_t *macinfo);
static int llc1_unbind(queue_t *q, mblk_t *mp);
static int llc1_subs_bind(queue_t *q, mblk_t *mp);
static int llc1_unitdata(queue_t *q, mblk_t *mp);
static int llc1_inforeq(queue_t *q, mblk_t *mp);
static int llc1attach(queue_t *q, mblk_t *mp);
static void llc1_send_bindreq(llc_mac_info_t *macinfo);
static int llc1_req_info(queue_t *q);
static int llc1_cmds(queue_t *q, mblk_t *mp);
static int llc1_setppa(struct ll_snioc *snioc);
static int llc1_getppa(llc_mac_info_t *macinfo, struct ll_snioc *snioc);
static int llc1_bind(queue_t *q, mblk_t *mp);
static int llc1unattach(queue_t *q, mblk_t *mp);
static int llc1_enable_multi(queue_t *q, mblk_t *mp);
static int llc1_disable_multi(queue_t *q, mblk_t *mp);
static int llc1_xid_req_res(queue_t *q, mblk_t *mp, int req_or_res);
static int llc1_test_req_res(queue_t *q, mblk_t *mp, int req_or_res);
static int llc1_local(struct ether_addr *addr, llc_mac_info_t *macinfo);
static int llc1_snap_match(llc1_t *lld, struct snaphdr *snap);

/*
 * the standard streams glue for defining the type of streams entity and the
 * operational parameters.
 */

static struct module_info llc1_minfo = {
	LLC1IDNUM,
	"llc1",
	0,
	LLC1_DEFMAX,
	LLC1_HIWATER,		/* high water mark */
	LLC1_LOWATER,		/* low water mark */
};

static struct qinit llc1_rint = {
	NULL,
	NULL,
	llc1_open,
	llc1_close,
	NULL,
	&llc1_minfo,
	NULL
};

static struct qinit llc1_wint = {
	llc1_uwput,
	llc1_uwsrv,
	NULL,
	NULL,
	NULL,
	&llc1_minfo,
	NULL
};

static struct qinit llc1_muxrint = {
	putq,
	llc1_lrsrv,
	NULL,
	NULL,
	NULL,
	&llc1_minfo,
	NULL
};

static struct qinit llc1_muxwint = {
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	&llc1_minfo,
	NULL
};

struct streamtab llc1_info = {
	&llc1_rint,
	&llc1_wint,
	&llc1_muxrint,
	&llc1_muxwint
};

/*
 * loadable module/driver wrapper this allows llc1 to be unloaded later
 */

#if !defined(BUILD_STATIC)
#include <sys/modctl.h>

/* define the "ops" structure for a STREAMS driver */
DDI_DEFINE_STREAM_OPS(llc1_ops, nulldev, nulldev, llc1_attach,
    llc1_detach, nodev, llc1_getinfo, D_MP | D_MTPERMOD, &llc1_info,
    ddi_quiesce_not_supported);

/*
 * Module linkage information for the kernel.
 */
static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module.  This one is a driver */
	"LLC Class 1 Driver",
	&llc1_ops,		/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modldrv, NULL
};

int
_init(void)
{
	return (mod_install(&modlinkage));
}

int
_fini(void)
{
	return (mod_remove(&modlinkage));
}

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

#endif

#ifdef LLC1_DEBUG
extern int llc1_debug = 0x0;

#endif

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

static struct llc1device llc1_device_list;

/*
 * llc1_attach - init time attach support When the hardware specific attach
 * is called, it must call this procedure with the device class structure
 */

static int
llc1_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
{
	if (cmd != DDI_ATTACH)
		return (DDI_FAILURE);

	/*
	 * there isn't any hardware but we do need to initialize things
	 */
	if (!(llc1_device_list.llc1_status & LLC1_ATTACHED)) {
		llc1_device_list.llc1_status |= LLC1_ATTACHED;
		rw_init(&llc1_device_list.llc1_rwlock, NULL, RW_DRIVER, NULL);

		/* make sure minor device lists are initialized */
		llc1_device_list.llc1_str_next =
		    llc1_device_list.llc1_str_prev =
		    (llc1_t *)&llc1_device_list.llc1_str_next;

		/* make sure device list is initialized */
		llc1_device_list.llc1_mac_next =
		    llc1_device_list.llc1_mac_prev =
		    (llc_mac_info_t *)&llc1_device_list.llc1_mac_next;
	}

	/*
	 * now do all the DDI stuff necessary
	 */

	ddi_set_driver_private(devinfo, &llc1_device_list);

	/*
	 * create the file system device node
	 */
	if (ddi_create_minor_node(devinfo, "llc1", S_IFCHR,
	    0, DDI_PSEUDO, CLONE_DEV) == DDI_FAILURE) {
		llc1error(devinfo, "ddi_create_minor_node failed");
		ddi_remove_minor_node(devinfo, NULL);
		return (DDI_FAILURE);
	}
	llc1_device_list.llc1_multisize = ddi_getprop(DDI_DEV_T_NONE,
	    devinfo, 0, "multisize", 0);
	if (llc1_device_list.llc1_multisize == 0)
		llc1_device_list.llc1_multisize = LLC1_MAX_MULTICAST;

	ddi_report_dev(devinfo);
	return (DDI_SUCCESS);
}

/*
 * llc1_detach standard kernel interface routine
 */

static int
llc1_detach(dev_info_t *dev, ddi_detach_cmd_t cmd)
{
	if (cmd != DDI_DETACH) {
		return (DDI_FAILURE);
	}
	if (llc1_device_list.llc1_ndevice > 0)
		return (DDI_FAILURE);
	/* remove all mutex and locks */
	rw_destroy(&llc1_device_list.llc1_rwlock);
	llc1_device_list.llc1_status = 0;	/* no longer attached */
	ddi_remove_minor_node(dev, NULL);
	return (DDI_SUCCESS);
}

/*
 * llc1_devinfo(dev, cmd, arg, result) standard kernel devinfo lookup
 * function
 */
/*ARGSUSED2*/
static int
llc1_getinfo(dev_info_t *dev, ddi_info_cmd_t cmd, void *arg, void **result)
{
	int error;

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (dev == NULL) {
			error = DDI_FAILURE;
		} else {
			*result = (void *)dev;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}

/*
 * llc1_open()
 * LLC1 open routine, called when device is opened by the user
 */

/*ARGSUSED2*/
static int
llc1_open(queue_t *q, dev_t *dev, int flag, int sflag, cred_t *cred)
{
	llc1_t *llc1;
	minor_t	minordev;
	int	status = 0;

	ASSERT(q);

	/*
	 * Stream already open, sucess.
	 */
	if (q->q_ptr)
		return (0);
	/*
	 * Serialize access through open/close this will serialize across all
	 * llc1 devices, but open and close are not frequent so should not
	 * induce much, if any delay.
	 */
	rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);

	if (sflag == CLONEOPEN) {
		/* need to find a minor dev */
		minordev = llc1_findminor(&llc1_device_list);
		if (minordev == 0) {
			rw_exit(&llc1_device_list.llc1_rwlock);
			return (ENXIO);
		}
		*dev = makedevice(getmajor(*dev), minordev);
	} else {
		minordev = getminor (*dev);
		if ((minordev > MAXMIN32) || (minordev == 0)) {
			rw_exit(&llc1_device_list.llc1_rwlock);
			return (ENXIO);
		}
	}

	/*
	 * get a per-stream structure and link things together so we
	 * can easily find them later.
	 */

	llc1 = kmem_zalloc(sizeof (llc1_t), KM_SLEEP);
	llc1->llc_qptr = q;
	WR(q)->q_ptr = q->q_ptr = (caddr_t)llc1;
	/*
	 * fill in the structure and state info
	 */
	llc1->llc_state = DL_UNATTACHED;
	llc1->llc_style = DL_STYLE2;
	llc1->llc_minor = minordev;

	mutex_init(&llc1->llc_lock, NULL, MUTEX_DRIVER, NULL);
	llc1insque(llc1, llc1_device_list.llc1_str_prev);
	rw_exit(&llc1_device_list.llc1_rwlock);
	qprocson(q);		/* start the queues running */
	return (status);
}

/*
 * llc1_close(q)
 * normal stream close call checks current status and cleans up
 * data structures that were dynamically allocated
 */
/*ARGSUSED1*/
static int
llc1_close(queue_t *q, int flag, cred_t *cred)
{
	llc1_t *llc1;

	ASSERT(q);
	ASSERT(q->q_ptr);

	qprocsoff(q);
	llc1 = (llc1_t *)q->q_ptr;
	rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);
	/* completely disassociate the stream from the device */
	q->q_ptr = WR(q)->q_ptr = NULL;

	(void) llc1remque(llc1); /* remove from active list */
	rw_exit(&llc1_device_list.llc1_rwlock);

	mutex_enter(&llc1->llc_lock);
	if (llc1->llc_state == DL_IDLE || llc1->llc_state == DL_UNBOUND) {
		llc1->llc_state = DL_UNBOUND;	/* force the issue */
	}

	if (llc1->llc_mcast != NULL) {
		int	i;

		for (i = 0; i < llc1_device_list.llc1_multisize; i++) {
			llc_mcast_t *mcast;

			if ((mcast = llc1->llc_mcast[i]) != NULL) {
				/*
				 * disable from stream and possibly
				 * lower stream
				 */
				if (llc1->llc_mac_info &&
				    llc1->llc_mac_info->llcp_flags &
				    LLC1_AVAILABLE)
					llc1_send_disable_multi(
					    llc1->llc_mac_info,
					    mcast);
				llc1->llc_mcast[i] = NULL;
			}
		}
		kmem_free(llc1->llc_mcast,
		    sizeof (llc_mcast_t *) * llc1->llc_multicnt);
		llc1->llc_mcast = NULL;
	}
	llc1->llc_state = DL_UNATTACHED;

	mutex_exit(&llc1->llc_lock);

	mutex_destroy(&llc1->llc_lock);

	kmem_free(llc1, sizeof (llc1_t));

	return (0);
}

/*
 * llc1_uwput()
 * general llc stream write put routine. Receives ioctl's from
 * user level and data from upper modules and processes them immediately.
 * M_PROTO/M_PCPROTO are queued for later processing by the service
 * procedure.
 */

static int
llc1_uwput(queue_t *q, mblk_t *mp)
{
	llc1_t *ld = (llc1_t *)(q->q_ptr);

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_wput(%x %x): type %d\n", q, mp, DB_TYPE(mp));
#endif
	switch (DB_TYPE(mp)) {

	case M_IOCTL:		/* no waiting in ioctl's */
		(void) llc1_ioctl(q, mp);
		break;

	case M_FLUSH:		/* canonical flush handling */
		if (*mp->b_rptr & FLUSHW)
			flushq(q, 0);

		if (*mp->b_rptr & FLUSHR) {
			flushq(RD(q), 0);
			*mp->b_rptr &= ~FLUSHW;
			qreply(q, mp);
		} else
			freemsg(mp);
		break;

		/* for now, we will always queue */
	case M_PROTO:
	case M_PCPROTO:
		(void) putq(q, mp);
		break;

	case M_DATA:
		/* fast data / raw support */
		if ((ld->llc_flags & (LLC_RAW | LLC_FAST)) == 0 ||
		    ld->llc_state != DL_IDLE) {
			(void) merror(q, mp, EPROTO);
			break;
		}
		/* need to do further checking */
		(void) putq(q, mp);
		break;

	default:
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCERRS)
			printf("llc1: Unexpected packet type from queue: %d\n",
			    mp->b_datap->db_type);
#endif
		freemsg(mp);
	}
	return (0);
}

/*
 * llc1_lrsrv()
 * called when data is put into the service queue from below.
 * Determines additional processing that might be needed and sends the data
 * upstream in the form of a Data Indication packet.
 */
static int
llc1_lrsrv(queue_t *q)
{
	mblk_t *mp;
	union DL_primitives *prim;
	llc_mac_info_t *macinfo = (llc_mac_info_t *)q->q_ptr;
	struct iocblk *iocp;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_rsrv(%x)\n", q);
	if (llc1_debug & LLCRECV) {
		printf("llc1_lrsrv: q=%x macinfo=%x", q, macinfo);
		if (macinfo == NULL) {
			printf("NULL macinfo");
			panic("null macinfo in lrsrv");
			/*NOTREACHED*/
		}
		printf("\n");
	}
#endif

	/*
	 * determine where message goes, then call the proper handler
	 */

	while ((mp = getq(q)) != NULL) {
		switch (DB_TYPE(mp)) {
		case M_PROTO:
		case M_PCPROTO:
			prim = (union DL_primitives *)mp->b_rptr;
			/* only some primitives ever get passed through */
			switch (prim->dl_primitive) {
			case DL_INFO_ACK:
				if (macinfo->llcp_flags & LLC1_LINKED) {
					/*
					 * we are in the midst of completing
					 * the I_LINK/I_PLINK and needed this
					 * info
					 */
					macinfo->llcp_flags &= ~LLC1_LINKED;
					macinfo->llcp_flags |= LLC1_AVAILABLE;
					macinfo->llcp_maxpkt =
					    prim->info_ack.dl_max_sdu;
					macinfo->llcp_minpkt =
					    prim->info_ack.dl_min_sdu;
					macinfo->llcp_type =
					    prim->info_ack.dl_mac_type;
					if (macinfo->llcp_type == DL_ETHER) {
						macinfo->llcp_type = DL_CSMACD;
						/*
						 * size of max header
						 * (including SNAP)
						 */
						macinfo->llcp_maxpkt -= 8;
					}
					macinfo->llcp_addrlen =
					    prim->info_ack.dl_addr_length -
					    ABS(prim->info_ack.dl_sap_length);

					bcopy(mp->b_rptr +
					    prim->info_ack.dl_addr_offset,
					    macinfo->llcp_macaddr,
					    macinfo->llcp_addrlen);
					bcopy(mp->b_rptr +
					    prim->info_ack.
					    dl_brdcst_addr_offset,
					    macinfo->llcp_broadcast,
					    prim->info_ack.
					    dl_brdcst_addr_length);

					if (prim->info_ack.dl_current_state ==
					    DL_UNBOUND)
						llc1_send_bindreq(macinfo);
					freemsg(mp);
					/*
					 * need to put the lower stream into
					 * DLRAW mode.  Currently only DL_ETHER
					 * or DL_CSMACD
					 */
					switch (macinfo->llcp_type) {
					case DL_ETHER:
					case DL_CSMACD:
						/*
						 * raw mode is optimal so ask
						 * for it * we might not get
						 * it but that's OK
						 */
						llc1_req_raw(macinfo);
						break;
					default:
						/*
						 * don't want raw mode so don't
						 * ask for it
						 */
						break;
					}
				} else {
					if (prim->info_ack.dl_current_state ==
					    DL_IDLE)
					/* address was wrong before */
					bcopy(mp->b_rptr +
					    prim->info_ack.dl_addr_offset,
					    macinfo->llcp_macaddr,
					    macinfo->llcp_addrlen);
					freemsg(mp);
				}
				break;
			case DL_BIND_ACK:
				/*
				 * if we had to bind, the macaddr is wrong
				 * so get it again
				 */
				freemsg(mp);
				(void) llc1_req_info(q);
				break;
			case DL_UNITDATA_IND:
				/* when not using raw mode we get these */
				(void) llc1_recv(macinfo, mp);
				break;
			case DL_ERROR_ACK:
				/* binding is a special case */
				if (prim->error_ack.dl_error_primitive ==
				    DL_BIND_REQ) {
					freemsg(mp);
					if (macinfo->llcp_flags & LLC1_BINDING)
						llc1_send_bindreq(macinfo);
				} else
					llc1_find_waiting(macinfo, mp,
					    prim->error_ack.dl_error_primitive);
				break;
			case DL_PHYS_ADDR_ACK:
				llc1_find_waiting(macinfo, mp,
				    DL_PHYS_ADDR_REQ);
				break;
			case DL_OK_ACK:
				if (prim->ok_ack.dl_correct_primitive ==
				    DL_BIND_REQ)
					macinfo->llcp_flags &= ~LLC1_BINDING;
				/* FALLTHROUGH */
			default:
				freemsg(mp);
			}
			break;

		case M_IOCACK:
			/* probably our DLIOCRAW completing */
			iocp = (struct iocblk *)mp->b_rptr;
			if ((macinfo->llcp_flags & LLC1_RAW_WAIT) &&
			    macinfo->llcp_iocid == iocp->ioc_id) {
				macinfo->llcp_flags &= ~LLC1_RAW_WAIT;
				/* we can use this form */
				macinfo->llcp_flags |= LLC1_USING_RAW;
				freemsg(mp);
				break;
			}
			/* need to find the correct queue */
			freemsg(mp);
			break;
		case M_IOCNAK:
			iocp = (struct iocblk *)mp->b_rptr;
			if ((macinfo->llcp_flags & LLC1_RAW_WAIT) &&
			    macinfo->llcp_iocid == iocp->ioc_id) {
				macinfo->llcp_flags &= ~LLC1_RAW_WAIT;
				freemsg(mp);
				break;
			}
			/* need to find the correct queue */
			freemsg(mp);
			break;
		case M_DATA:
			llc1_recv(macinfo, mp);
			break;
		}
	}
	return (0);
}

/*
 * llc1_uwsrv - Incoming messages are processed according to the DLPI
 * protocol specification
 */

static int
llc1_uwsrv(queue_t *q)
{
	mblk_t *mp;
	llc1_t *lld = (llc1_t *)q->q_ptr;
	union DL_primitives *prim;
	int	err;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_wsrv(%x)\n", q);
#endif


	while ((mp = getq(q)) != NULL) {
		switch (mp->b_datap->db_type) {
		case M_PROTO:	/* Will be an DLPI message of some type */
		case M_PCPROTO:
			if ((err = llc1_cmds(q, mp)) != LLCE_OK) {
				prim = (union DL_primitives *)mp->b_rptr;
				if (err == LLCE_NOBUFFER || err == DL_SYSERR) {
					/* quit while we're ahead */
					lld->llc_stats->llcs_nobuffer++;
#ifdef LLC1_DEBUG
					if (llc1_debug & LLCERRS)
						printf(
"llc1_cmds: nonfatal err=%d\n",
						    err);
#endif
					(void) putbq(q, mp);
					return (0);

				} else {
					dlerrorack(q, mp,
					    prim->dl_primitive,
					    err, 0);
				}
			}
			break;
		case M_DATA:
			/*
			 * retry of a previously processed
			 * UNITDATA_REQ or is a RAW message from
			 * above
			 */

			mutex_enter(&lld->llc_lock);
			putnext(lld->llc_mac_info->llcp_queue, mp);
			mutex_exit(&lld->llc_lock);
			freemsg(mp);	/* free on success */
			break;

			/* This should never happen */
		default:
#ifdef LLC1_DEBUG
			if (llc1_debug & LLCERRS)
				printf("llc1_wsrv: type(%x) not supported\n",
				    mp->b_datap->db_type);
#endif
			freemsg(mp);	/* unknown types are discarded */
			break;
		}
	}
	return (0);
}

/*
 * llc1_multicast used to determine if the address is a multicast address for
 * this user.
 */
int
llc1_multicast(struct ether_addr *addr, llc1_t *lld)
{
	int i;

	if (lld->llc_mcast)
		for (i = 0; i < lld->llc_multicnt; i++)
			if (lld->llc_mcast[i] &&
			    lld->llc_mcast[i]->llcm_refcnt &&
			    bcmp(lld->llc_mcast[i]->llcm_addr,
			    addr->ether_addr_octet, ETHERADDRL) == 0)
				return (1);
	return (0);
}

/*
 * llc1_ioctl handles all ioctl requests passed downstream. This routine is
 * passed a pointer to the message block with the ioctl request in it, and a
 * pointer to the queue so it can respond to the ioctl request with an ack.
 */

int	llc1_doreqinfo;

static void
llc1_ioctl(queue_t *q, mblk_t *mp)
{
	struct iocblk *iocp;
	llc1_t *lld;
	struct linkblk *link;
	llc_mac_info_t *macinfo;
	mblk_t *tmp;
	int error;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_ioctl(%x %x)\n", q, mp);
#endif
	lld = (llc1_t *)q->q_ptr;
	iocp = (struct iocblk *)mp->b_rptr;
	switch (iocp->ioc_cmd) {
		/* XXX need to lock the data structures */
	case I_PLINK:
	case I_LINK:
		link = (struct linkblk *)mp->b_cont->b_rptr;
		tmp = allocb(sizeof (llc_mac_info_t), BPRI_MED);
		if (tmp == NULL) {
			(void) miocnak(q, mp, 0, ENOSR);
			return;
		}
		bzero(tmp->b_rptr, sizeof (llc_mac_info_t));
		macinfo = (llc_mac_info_t *)tmp->b_rptr;
		macinfo->llcp_mb = tmp;
		macinfo->llcp_next = macinfo->llcp_prev = macinfo;
		macinfo->llcp_queue = link->l_qbot;
		macinfo->llcp_lindex = link->l_index;
		/* tentative */
		macinfo->llcp_ppa = --llc1_device_list.llc1_nextppa;
		llc1_device_list.llc1_ndevice++;
		macinfo->llcp_flags |= LLC1_LINKED | LLC1_DEF_PPA;
		macinfo->llcp_lqtop = q;
		macinfo->llcp_data = NULL;

		/* need to do an info_req before an info_req or attach */

		rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);
		llc1insque(macinfo, llc1_device_list.llc1_mac_prev);
		macinfo->llcp_queue->q_ptr = RD(macinfo->llcp_queue)->q_ptr =
		    (caddr_t)macinfo;
		llc1_init_kstat(macinfo);
		rw_exit(&llc1_device_list.llc1_rwlock);

		/* initiate getting the info */
		(void) llc1_req_info(macinfo->llcp_queue);

		miocack(q, mp, 0, 0);
		return;

	case I_PUNLINK:
	case I_UNLINK:
		link = (struct linkblk *)mp->b_cont->b_rptr;
		rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);
		for (macinfo = llc1_device_list.llc1_mac_next;
		    macinfo != NULL &&
		    macinfo !=
		    (llc_mac_info_t *)&llc1_device_list.llc1_mac_next;
		    macinfo = macinfo->llcp_next) {
			if (macinfo->llcp_lindex == link->l_index &&
			    macinfo->llcp_queue == link->l_qbot) {
				/* found it */

				ASSERT(macinfo->llcp_next);

			    /* remove from device list */
				llc1_device_list.llc1_ndevice--;
				llc1remque(macinfo);

			    /* remove any mcast structs */
				if (macinfo->llcp_mcast != NULL) {
				kmem_free(macinfo->llcp_mcast,
				    sizeof (llc_mcast_t) *
				    llc1_device_list.llc1_multisize);
				macinfo->llcp_mcast = NULL;
				}

			    /* remove any kstat counters */
				if (macinfo->llcp_kstatp != NULL)
				llc1_uninit_kstat(macinfo);
				if (macinfo->llcp_mb != NULL)
				freeb(macinfo->llcp_mb);

				lld->llc_mac_info = NULL;

				miocack(q, mp, 0, 0);

			    /* finish any necessary setup */
				if (llc1_device_list.llc1_ndevice == 0)
				llc1_device_list.llc1_nextppa = 0;

				rw_exit(&llc1_device_list.llc1_rwlock);
				return;
			}
		}
		rw_exit(&llc1_device_list.llc1_rwlock);
		/*
		 * what should really be done here -- force errors on all
		 * streams?
		 */
		miocnak(q, mp, 0, EINVAL);
		return;

	case L_SETPPA:
		error = miocpullup(mp, sizeof (struct ll_snioc));
		if (error != 0) {
			miocnak(q, mp, 0, error);
			return;
		}

		if (llc1_setppa((struct ll_snioc *)mp->b_cont->b_rptr) >= 0) {
			miocack(q, mp, 0, 0);
			return;
		}
		miocnak(q, mp, 0, EINVAL);
		return;

	case L_GETPPA:
		if (mp->b_cont == NULL) {
			mp->b_cont = allocb(sizeof (struct ll_snioc), BPRI_MED);
			if (mp->b_cont == NULL) {
				miocnak(q, mp, 0, ENOSR);
				return;
			}
			mp->b_cont->b_wptr =
			    mp->b_cont->b_rptr + sizeof (struct ll_snioc);
		} else {
			error = miocpullup(mp, sizeof (struct ll_snioc));
			if (error != 0) {
				miocnak(q, mp, 0, error);
				return;
			}
		}

		lld = (llc1_t *)q->q_ptr;
		if (llc1_getppa(lld->llc_mac_info,
		    (struct ll_snioc *)mp->b_cont->b_rptr) >= 0)
			miocack(q, mp, 0, 0);
		else
			miocnak(q, mp, 0, EINVAL);
		return;
	default:
		miocnak(q, mp, 0, EINVAL);
	}
}

/*
 * llc1_setppa(snioc) this function sets the real PPA number for a previously
 * I_LINKED stream. Be careful to select the macinfo struct associated
 * with our llc struct, to avoid erroneous references.
 */

static int
llc1_setppa(struct ll_snioc *snioc)
{
	llc_mac_info_t *macinfo;

	for (macinfo = llc1_device_list.llc1_mac_next;
	    macinfo != (llc_mac_info_t *)&llc1_device_list.llc1_mac_next;
	    macinfo = macinfo->llcp_next)
		if (macinfo->llcp_lindex == snioc->lli_index &&
		    (macinfo->llcp_flags & LLC1_DEF_PPA)) {
			macinfo->llcp_flags &= ~LLC1_DEF_PPA;
			macinfo->llcp_ppa = snioc->lli_ppa;
			return (0);
		}
	return (-1);
}

/*
 * llc1_getppa(macinfo, snioc) returns the PPA for this stream
 */
static int
llc1_getppa(llc_mac_info_t *macinfo, struct ll_snioc *snioc)
{
	if (macinfo == NULL)
		return (-1);
	snioc->lli_ppa = macinfo->llcp_ppa;
	snioc->lli_index = macinfo->llcp_lindex;
	return (0);
}

/*
 * llc1_cmds - process the DL commands as defined in dlpi.h
 */
static int
llc1_cmds(queue_t *q, mblk_t *mp)
{
	union DL_primitives *dlp;
	llc1_t *llc = (llc1_t *)q->q_ptr;
	int	result = 0;
	llc_mac_info_t *macinfo = llc->llc_mac_info;

	dlp = (union DL_primitives *)mp->b_rptr;
#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_cmds(%x, %x):dlp=%x, dlp->dl_primitive=%d\n",
		    q, mp, dlp, dlp->dl_primitive);
#endif
	mutex_enter(&llc->llc_lock);
	rw_enter(&llc1_device_list.llc1_rwlock, RW_READER);

	switch (dlp->dl_primitive) {
	case DL_BIND_REQ:
		result = llc1_bind(q, mp);
		break;

	case DL_UNBIND_REQ:
		result = llc1_unbind(q, mp);
		break;

	case DL_SUBS_BIND_REQ:
		result = llc1_subs_bind(q, mp);
		break;

	case DL_SUBS_UNBIND_REQ:
		result = llc1_subs_unbind();
		break;

	case DL_UNITDATA_REQ:
		result = llc1_unitdata(q, mp);
		break;

	case DL_INFO_REQ:
		result = llc1_inforeq(q, mp);
		break;

	case DL_ATTACH_REQ:
		result = llc1attach(q, mp);
		break;

	case DL_DETACH_REQ:
		result = llc1unattach(q, mp);
		break;

	case DL_ENABMULTI_REQ:
		result = llc1_enable_multi(q, mp);
		break;

	case DL_DISABMULTI_REQ:
		result = llc1_disable_multi(q, mp);
		break;

	case DL_XID_REQ:
		result = llc1_xid_req_res(q, mp, 0);
		break;

	case DL_XID_RES:
		result = llc1_xid_req_res(q, mp, 1);
		break;

	case DL_TEST_REQ:
		result = llc1_test_req_res(q, mp, 0);
		break;

	case DL_TEST_RES:
		result = llc1_test_req_res(q, mp, 1);
		break;

	case DL_SET_PHYS_ADDR_REQ:
		result = DL_NOTSUPPORTED;
		break;

	case DL_PHYS_ADDR_REQ:
		if (llc->llc_state != DL_UNATTACHED && macinfo) {
			llc->llc_waiting_for = dlp->dl_primitive;
			putnext(WR(macinfo->llcp_queue), mp);
			result = LLCE_OK;
		} else {
			result = DL_OUTSTATE;
		}
		break;

	case DL_PROMISCON_REQ:
	case DL_PROMISCOFF_REQ:
		result = DL_NOTSUPPORTED;
		break;

	default:
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCERRS)
			printf("llc1_cmds: Received unknown primitive: %d\n",
			    dlp->dl_primitive);
#endif
		result = DL_BADPRIM;
		break;
	}
	rw_exit(&llc1_device_list.llc1_rwlock);
	mutex_exit(&llc->llc_lock);
	return (result);
}

/*
 * llc1_bind - determine if a SAP is already allocated and whether it is
 * legal to do the bind at this time
 */
static int
llc1_bind(queue_t *q, mblk_t *mp)
{
	int	sap;
	dl_bind_req_t *dlp;
	llc1_t *lld = (llc1_t *)q->q_ptr;

	ASSERT(lld);

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_bind(%x %x)\n", q, mp);
#endif

	dlp = (dl_bind_req_t *)mp->b_rptr;
	sap = dlp->dl_sap;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCPROT)
		printf("llc1_bind: lsap=%x\n", sap);
#endif

	if (lld->llc_mac_info == NULL)
		return (DL_OUTSTATE);

	if (lld->llc_qptr && lld->llc_state != DL_UNBOUND) {
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCERRS)
			printf("llc1_bind: stream bound/not attached (%d)\n",
			    lld->llc_state);
#endif
		return (DL_OUTSTATE);
	}

	if (dlp->dl_service_mode != DL_CLDLS || dlp->dl_max_conind != 0) {
		return (DL_UNSUPPORTED);
	}
	/*
	 * prohibit group saps.	An exception is the broadcast sap which is,
	 * unfortunately, used by SUNSelect to indicate Novell Netware in
	 * 802.3 mode.	Really should use a very non-802.2 SAP like 0xFFFF
	 * or -2.
	 */

	if (sap == 0 || (sap <= 0xFF && (sap & 1 && sap != 0xFF)) ||
	    sap > 0xFFFF) {
		return (DL_BADSAP);
	}
	lld->llc_state = DL_BIND_PENDING;

	/* if we fall through, then the SAP is legal */
	if (sap == 0xFF) {
		if (lld->llc_mac_info->llcp_type == DL_CSMACD)
			sap = LLC_NOVELL_SAP;
		else
			return (DL_BADSAP);
	}
	lld->llc_sap = sap;

	if (sap > 0xFF) {
		ushort_t snapsap = htons(sap);
		/* this is SNAP, so set things up */
		lld->llc_snap[3] = ((uchar_t *)&snapsap)[0];
		lld->llc_snap[4] = ((uchar_t *)&snapsap)[1];
		/* mark as SNAP but allow OID to be added later */
		lld->llc_flags |= LLC_SNAP;
		lld->llc_sap = LLC_SNAP_SAP;
	}

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCPROT)
		printf("llc1_bind: ok - type = %d\n", lld->llc_type);
#endif

	if (dlp->dl_xidtest_flg & DL_AUTO_XID)
		lld->llc_flags |= LLC1_AUTO_XID;
	if (dlp->dl_xidtest_flg & DL_AUTO_TEST)
		lld->llc_flags |= LLC1_AUTO_TEST;

	/* ACK the BIND, if possible */

	dlbindack(q, mp, sap, lld->llc_mac_info->llcp_macaddr, 6, 0, 0);

	lld->llc_state = DL_IDLE;	/* bound and ready */

	return (LLCE_OK);
}

/*
 * llc1_unbind - perform an unbind of an LSAP or ether type on the stream.
 * The stream is still open and can be re-bound.
 */
static int
llc1_unbind(queue_t *q, mblk_t *mp)
{
	llc1_t *lld;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_unbind(%x %x)\n", q, mp);
#endif
	lld = (llc1_t *)q->q_ptr;

	if (lld->llc_mac_info == NULL)
		return (DL_OUTSTATE);

	if (lld->llc_state != DL_IDLE) {
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCERRS)
			printf("llc1_unbind: wrong state (%d)\n",
			    lld->llc_state);
#endif
		return (DL_OUTSTATE);
	}
	lld->llc_state = DL_UNBIND_PENDING;
	lld->llc_flags &= ~(LLC_SNAP|LLC_SNAP_OID); /* just in case */
	dlokack(q, mp, DL_UNBIND_REQ);
	lld->llc_state = DL_UNBOUND;
	return (LLCE_OK);
}

/*
 * llc1_inforeq - generate the response to an info request
 */
static int
llc1_inforeq(queue_t *q, mblk_t *mp)
{
	llc1_t *lld;
	mblk_t *nmp;
	dl_info_ack_t *dlp;
	int	bufsize;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_inforeq(%x %x)\n", q, mp);
#endif
	lld = (llc1_t *)q->q_ptr;
	ASSERT(lld);
	if (lld->llc_mac_info == NULL)
		bufsize = sizeof (dl_info_ack_t) + ETHERADDRL;
	else
		bufsize = sizeof (dl_info_ack_t) +
		    2 * lld->llc_mac_info->llcp_addrlen + 2;

	nmp = mexchange(q, mp, bufsize, M_PCPROTO, DL_INFO_ACK);

	if (nmp) {
		nmp->b_wptr = nmp->b_rptr + sizeof (dl_info_ack_t);
		dlp = (dl_info_ack_t *)nmp->b_rptr;
		bzero(dlp, DL_INFO_ACK_SIZE);
		dlp->dl_primitive = DL_INFO_ACK;
		if (lld->llc_mac_info)
			dlp->dl_max_sdu = lld->llc_mac_info->llcp_maxpkt;
		dlp->dl_min_sdu = 0;
		dlp->dl_mac_type = lld->llc_type;
		dlp->dl_service_mode = DL_CLDLS;
		dlp->dl_current_state = lld->llc_state;
		dlp->dl_provider_style =
		    (lld->llc_style == 0) ? lld->llc_style : DL_STYLE2;

		/* now append physical address */
		if (lld->llc_mac_info) {
			dlp->dl_addr_length = lld->llc_mac_info->llcp_addrlen;
			dlp->dl_addr_offset = DL_INFO_ACK_SIZE;
			nmp->b_wptr += dlp->dl_addr_length + 1;
			bcopy(lld->llc_mac_info->llcp_macaddr,
			    ((caddr_t)dlp) + dlp->dl_addr_offset,
			    lld->llc_mac_info->llcp_addrlen);
			if (lld->llc_state == DL_IDLE) {
				dlp->dl_sap_length = -1; /* 1 byte on end */
				*(((caddr_t)dlp) + dlp->dl_addr_offset +
				    dlp->dl_addr_length) = lld->llc_sap;
				dlp->dl_addr_length += 1;
			}
			/* and the broadcast address */
			dlp->dl_brdcst_addr_length =
			    lld->llc_mac_info->llcp_addrlen;
			dlp->dl_brdcst_addr_offset =
			    dlp->dl_addr_offset + dlp->dl_addr_length;
			nmp->b_wptr += dlp->dl_brdcst_addr_length;
			bcopy(lld->llc_mac_info->llcp_broadcast,
			    ((caddr_t)dlp) + dlp->dl_brdcst_addr_offset,
			    lld->llc_mac_info->llcp_addrlen);
		} else {
			dlp->dl_addr_length = 0; /* not attached yet */
			dlp->dl_addr_offset = NULL;
			dlp->dl_sap_length = 0; /* 1 bytes on end */
		}
		dlp->dl_version = DL_VERSION_2;
		qreply(q, nmp);
	}
	return (LLCE_OK);
}

/*
 * llc1_unitdata
 * send a datagram.  Destination address/lsap is in M_PROTO
 * message (first mblock), data is in remainder of message.
 *
 * NOTE: We are reusing the DL_unitdata_req mblock; if llc header gets any
 * bigger, recheck to make sure it still fits!	We assume that we have a
 * 64-byte dblock for this, since a DL_unitdata_req is 20 bytes and the next
 * larger dblock size is 64.
 */
static int
llc1_unitdata(queue_t *q, mblk_t *mp)
{
	llc1_t *lld = (llc1_t *)q->q_ptr;
	dl_unitdata_req_t *dlp = (dl_unitdata_req_t *)mp->b_rptr;
	struct ether_header *hdr;
	struct llcaddr *llcp;
	mblk_t *nmp;
	long	msglen;
	struct llchdr *llchdr;
	llc_mac_info_t *macinfo;
	int xmt_type = 0;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_unitdata(%x %x)\n", q, mp);
#endif

	if ((macinfo = lld->llc_mac_info) == NULL)
		return (DL_OUTSTATE);

	if (lld->llc_state != DL_IDLE) {
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCERRS)
			printf("llc1_unitdata: wrong state (%d)\n",
			    lld->llc_state);
#endif
		return (DL_OUTSTATE);
	}

	/* need the destination address in all cases */
	llcp = (struct llcaddr *)((caddr_t)dlp + dlp->dl_dest_addr_offset);

	if (macinfo->llcp_flags & LLC1_USING_RAW) {
		/*
		 * make a valid header for transmission
		 */

	    /* need a buffer big enough for the headers */
		nmp = allocb(macinfo->llcp_addrlen * 2 + 2 + 8, BPRI_MED);
		hdr = (struct ether_header *)nmp->b_rptr;
		msglen = msgdsize(mp);

	    /* fill in type dependent fields */
		switch (lld->llc_type) {
		case DL_CSMACD: /* 802.3 CSMA/CD */
		nmp->b_wptr = nmp->b_rptr + LLC1_CSMACD_HDR_SIZE;
		llchdr = (struct llchdr *)nmp->b_wptr;
		bcopy(llcp->llca_addr,
		    hdr->ether_dhost.ether_addr_octet,
		    ETHERADDRL);
		bcopy(macinfo->llcp_macaddr,
		    hdr->ether_shost.ether_addr_octet,
		    ETHERADDRL);

		if (lld->llc_sap != LLC_NOVELL_SAP) {
			/* set length with llc header size */
			hdr->ether_type = ntohs(msglen +
			    sizeof (struct llchdr));

			/* need an LLC header, otherwise is Novell */
			/* bound sap is always source */
			llchdr->llc_ssap = lld->llc_sap;

			/* destination sap */
			llchdr->llc_dsap = llcp->llca_sap;

			/* always Unnumbered Information */
			llchdr->llc_ctl = LLC_UI;

			nmp->b_wptr += sizeof (struct llchdr);

			if (lld->llc_flags & LLC_SNAP) {
				bcopy(lld->llc_snap, nmp->b_wptr, 5);
				llchdr->llc_dsap = LLC_SNAP_SAP;
				nmp->b_wptr += 5;
			}
		} else {
			/* set length without llc header size */
			hdr->ether_type = ntohs(msglen);

			/* we don't do anything else for Netware */
		}

		if (ismulticast(hdr->ether_dhost.ether_addr_octet)) {
			if (bcmp(hdr->ether_dhost.ether_addr_octet,
			    macinfo->llcp_broadcast, ETHERADDRL) == 0)
				xmt_type = 2;
			else
				xmt_type = 1;
		}

		break;

		default:		/* either RAW or unknown, send as is */
		break;
		}
		DB_TYPE(nmp) = M_DATA; /* ether/llc header is data */
		nmp->b_cont = mp->b_cont;	/* use the data given */
		freeb(mp);
		mp = nmp;
	} else {
	    /* need to format a DL_UNITDATA_REQ with LLC1 header inserted */
		nmp = allocb(sizeof (struct llchdr)+sizeof (struct snaphdr),
		    BPRI_MED);
		if (nmp == NULL)
		return (DL_UNDELIVERABLE);
		llchdr = (struct llchdr *)(nmp->b_rptr);
		nmp->b_wptr += sizeof (struct llchdr);
		llchdr->llc_dsap = llcp->llca_sap;
		llchdr->llc_ssap = lld->llc_sap;
		llchdr->llc_ctl = LLC_UI;

		/*
		 * if we are using SNAP, insert the header here
		 */
		if (lld->llc_flags & LLC_SNAP) {
			bcopy(lld->llc_snap, nmp->b_wptr, 5);
			nmp->b_wptr += 5;
		}
		nmp->b_cont = mp->b_cont;
		mp->b_cont = nmp;
		nmp = mp;
		if (ismulticast(llcp->llca_addr)) {
			if (bcmp(llcp->llca_addr,
			    macinfo->llcp_broadcast, ETHERADDRL) == 0)
				xmt_type = 2;
			else
				xmt_type = 1;
		}
	}
	if (canput(macinfo->llcp_queue)) {
		lld->llc_stats->llcs_bytexmt += msgdsize(mp);
		lld->llc_stats->llcs_pktxmt++;
		switch (xmt_type) {
		case 1:
			macinfo->llcp_stats.llcs_multixmt++;
			break;
		case 2:
			macinfo->llcp_stats.llcs_brdcstxmt++;
			break;
		}

		putnext(macinfo->llcp_queue, mp);
		return (LLCE_OK);	/* this is almost correct, the result */
	} else {
		lld->llc_stats->llcs_nobuffer++;
	}
	if (nmp != NULL)
		freemsg(nmp);	/* free on failure */
	return (LLCE_OK);
}

/*
 * llc1_recv(macinfo, mp)
 * called with an ethernet packet in a mblock; must decide
 * whether packet is for us and which streams to queue it to. This routine is
 * called with locally originated packets for loopback.
 */
static void
llc1_recv(llc_mac_info_t *macinfo, mblk_t *mp)
{
	struct ether_addr *addr;
	llc1_t *lld;
	mblk_t *nmp, *udmp;
	int	i, nmcast = 0, statcnt_normal = 0, statcnt_brdcst = 0;
	int valid, msgsap;
	struct llchdr *llchdr;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCTRACE)
		printf("llc1_recv(%x, %x)\n", mp, macinfo);
#endif

	if (DB_TYPE(mp) == M_PROTO) {
		dl_unitdata_ind_t *udata;

		/* check to see if really LLC1 XXX */
		/* also need to make sure to keep address info */
		nmp = mp;
		udata = (dl_unitdata_ind_t *)(nmp->b_rptr);
		addr = (struct ether_addr *)(nmp->b_rptr +
		    udata->dl_dest_addr_offset);
		llchdr = (struct llchdr *)(nmp->b_cont->b_rptr);
		if (macinfo->llcp_type == DL_CSMACD) {
			i = ((struct llcsaddr *)addr)->llca_ssap;
			if (i < 60) {
				valid = adjmsg(mp->b_cont, i - msgdsize(mp));
			}
		}
	} else {
		struct ether_header *hdr;

		/* Note that raw mode currently assumes Ethernet */
		nmp = NULL;
		hdr = (struct ether_header *)mp->b_rptr;
		addr = &hdr->ether_dhost;
		llchdr = (struct llchdr *)(mp->b_rptr +
		    sizeof (struct ether_header));
		i = (ushort_t)ntohs(hdr->ether_type);
		if (i < 60) {
			(void) adjmsg(mp, i + sizeof (struct ether_header) -
			    msgdsize(mp));
		}
	}
	udmp = NULL;

	msgsap = llchdr->llc_dsap;

#ifdef LLC1_DEBUG
	if (llc1_debug & LLCRECV) {
		printf("llc1_recv: machdr=<%s>\n", ether_sprintf(addr));
	}
#endif

	if (llc1_broadcast(addr, macinfo)) {
		valid = 2;	/* 2 means valid but multicast */
		statcnt_brdcst = 1;
	} else {
		valid = llc1_local(addr, macinfo);
		statcnt_normal = msgdsize(mp);
	}

	/*
	 * Note that the NULL SAP is a special case.  It is associated with
	 * the MAC layer and not the LLC layer so should be handled
	 * independently of any STREAM.
	 */
	if (msgsap == LLC_NULL_SAP) {
		/* only XID and TEST ever processed, UI is dropped */
		if ((llchdr->llc_ctl & ~LLC_P) == LLC_XID)
			mp = llc1_xid_reply(macinfo, mp, 0);
		else if ((llchdr->llc_ctl & ~LLC_P) == LLC_TEST)
			mp = llc1_test_reply(macinfo, mp, 0);
	} else
		for (lld = llc1_device_list.llc1_str_next;
		    lld != (llc1_t *)&llc1_device_list.llc1_str_next;
		    lld = lld->llc_next) {

			/*
			 * is this a potentially usable SAP on the
			 * right MAC layer?
			 */
			if (lld->llc_qptr == NULL ||
			    lld->llc_state != DL_IDLE ||
			    lld->llc_mac_info != macinfo) {
				continue;
			}
#ifdef LLC1_DEBUG
			if (llc1_debug & LLCRECV)
				printf(
"llc1_recv: type=%d, sap=%x, pkt-dsap=%x\n",
				    lld->llc_type, lld->llc_sap,
				    msgsap);
#endif
			if (!valid && ismulticast(addr->ether_addr_octet) &&
			    lld->llc_multicnt > 0 &&
			    llc1_multicast(addr, lld)) {
				valid |= 4;
			} else if (lld->llc_flags & LLC_PROM)
				/* promiscuous mode */
				valid = 1;

			if ((lld->llc_flags & LLC_PROM) ||
				/* promiscuous streams */
			    (valid &&
			    (lld->llc_sap == msgsap ||
			    msgsap == LLC_GLOBAL_SAP))) {
				/* sap matches */
				if (msgsap == LLC_SNAP_SAP &&
				    (lld->llc_flags & (LLC_SNAP|LLC_PROM)) ==
				    LLC_SNAP) {
					if (!llc1_snap_match(lld,
					    (struct snaphdr *)(llchdr+1)))
						continue;
				}
				if (!canputnext(RD(lld->llc_qptr))) {
#ifdef LLC1_DEBUG
					if (llc1_debug & LLCRECV)
						printf(
"llc1_recv: canput failed\n");
#endif
					lld->llc_stats->llcs_blocked++;
					continue;
				}
				/* check for Novell special handling */
				if (msgsap == LLC_GLOBAL_SAP &&
				    lld->llc_sap == LLC_NOVELL_SAP &&
				    llchdr->llc_ssap == LLC_GLOBAL_SAP) {

					/* A Novell packet */
					nmp = llc1_form_udata(lld, macinfo, mp);
					continue;
				}
				switch (llchdr->llc_ctl) {
				case LLC_UI:
					/*
					 * this is an Unnumbered Information
					 * packet so form a DL_UNITDATA_IND and
					 * send to user
					 */
					nmp = llc1_form_udata(lld, macinfo, mp);
					break;

				case LLC_XID:
				case LLC_XID | LLC_P:
					/*
					 * this is either an XID request or
					 * response. We either handle directly
					 * (if user hasn't requested to handle
					 * itself) or send to user. We also
					 * must check if a response if user
					 * handled so that we can send correct
					 * message form
					 */
					if (lld->llc_flags & LLC1_AUTO_XID) {
						nmp = llc1_xid_reply(macinfo,
						    mp, lld->llc_sap);
					} else {
						/*
						 * hand to the user for
						 * handling. if this is a
						 * "request", generate a
						 * DL_XID_IND.	If it is a
						 * "response" to one of our
						 * requests, generate a
						 * DL_XID_CON.
						 */
						nmp = llc1_xid_ind_con(lld,
						    macinfo, mp);
					}
					macinfo->llcp_stats.llcs_xidrcv++;
					break;

				case LLC_TEST:
				case LLC_TEST | LLC_P:
					/*
					 * this is either a TEST request or
					 * response.  We either handle
					 * directly (if user hasn't
					 * requested to handle itself)
					 * or send to user.  We also
					 * must check if a response if
					 * user handled so that we can
					 * send correct message form
					 */
					if (lld->llc_flags & LLC1_AUTO_TEST) {
						nmp = llc1_test_reply(macinfo,
						    mp, lld->llc_sap);
					} else {
						/*
						 * hand to the user for
						 * handling. if this is
						 * a "request",
						 * generate a
						 * DL_TEST_IND. If it
						 * is a "response" to
						 * one of our requests,
						 * generate a
						 * DL_TEST_CON.
						 */
						nmp = llc1_test_ind_con(lld,
						    macinfo, mp);
					}
					macinfo->llcp_stats.llcs_testrcv++;
					break;
				default:
					nmp = mp;
					break;
				}
				mp = nmp;
			}
		}
	if (mp != NULL)
		freemsg(mp);
	if (udmp != NULL)
		freeb(udmp);
	if (nmcast > 0)
		macinfo->llcp_stats.llcs_multircv++;
	if (statcnt_brdcst) {
		macinfo->llcp_stats.llcs_brdcstrcv++;
	}
	if (statcnt_normal) {
		macinfo->llcp_stats.llcs_bytercv += statcnt_normal;
		macinfo->llcp_stats.llcs_pktrcv++;
	}
}

/*
 * llc1_local - check to see if the message is addressed to this system by
 * comparing with the board's address.
 */
static int
llc1_local(struct ether_addr *addr, llc_mac_info_t *macinfo)
{
	return (bcmp(addr->ether_addr_octet, macinfo->llcp_macaddr,
	    macinfo->llcp_addrlen) == 0);
}

/*
 * llc1_broadcast - check to see if a broadcast address is the destination of
 * this received packet
 */
static int
llc1_broadcast(struct ether_addr *addr, llc_mac_info_t *macinfo)
{
	return (bcmp(addr->ether_addr_octet, macinfo->llcp_broadcast,
	    macinfo->llcp_addrlen) == 0);
}

/*
 * llc1attach(q, mp) DLPI DL_ATTACH_REQ this attaches the stream to a PPA
 */
static int
llc1attach(queue_t *q, mblk_t *mp)
{
	dl_attach_req_t *at;
	llc_mac_info_t *mac;
	llc1_t *llc = (llc1_t *)q->q_ptr;

	at = (dl_attach_req_t *)mp->b_rptr;

	if (llc->llc_state != DL_UNATTACHED) {
		return (DL_OUTSTATE);
	}
	llc->llc_state = DL_ATTACH_PENDING;

	if (rw_tryupgrade(&llc1_device_list.llc1_rwlock) == 0) {
		/*
		 * someone else has a lock held.  To avoid deadlock,
		 * release the READER lock and block on a WRITER
		 * lock.  This will let things continue safely.
		 */
		rw_exit(&llc1_device_list.llc1_rwlock);
		rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);
	}

	for (mac = llc1_device_list.llc1_mac_next;
	    mac != (llc_mac_info_t *)(&llc1_device_list.llc1_mac_next);
	    mac = mac->llcp_next) {
		ASSERT(mac);
		if (mac->llcp_ppa == at->dl_ppa && mac->llcp_lqtop == q) {
			/*
			 * We may have found the correct PPA
			 * check to see if linking has finished.
			 * Use explicit flag checks for incorrect
			 * state, and use negative values for "tenative"
			 * llcp_ppas, to avoid erroneous attaches.
			 */
			if (mac->llcp_flags &
			    (LLC1_LINKED|LLC1_DEF_PPA)) {
				return (DL_INITFAILED);
			} else if (!(mac->llcp_flags & LLC1_AVAILABLE)) {
				return (DL_BADPPA);
			}

			/* this links us to the PPA */
			mac->llcp_nstreams++;
			llc->llc_mac_info = mac;

			llc->llc_state = DL_UNBOUND; /* now ready for action */
			llc->llc_stats = &mac->llcp_stats;
			dlokack(q, mp, DL_ATTACH_REQ);

			return (LLCE_OK);
		}
	}
	llc->llc_state = DL_UNATTACHED;
	return (DL_BADPPA);
}

/*
 * llc1unattach(q, mp) DLPI DL_DETACH_REQ detaches the mac layer from the
 * stream
 */
static int
llc1unattach(queue_t *q, mblk_t *mp)
{
	llc1_t *llc = (llc1_t *)q->q_ptr;
	int	state;
	int	i;

	state = llc->llc_state;
	if (state != DL_UNBOUND)
		return (DL_OUTSTATE);

	/* can now detach from the PPA */
	llc->llc_state = DL_DETACH_PENDING;

	if (rw_tryupgrade(&llc1_device_list.llc1_rwlock) == 0) {
		/*
		 * someone else has a lock held.  To avoid deadlock,
		 * release the READER lock and block on a WRITER
		 * lock.  This will let things continue safely.
		 */
		rw_exit(&llc1_device_list.llc1_rwlock);
		rw_enter(&llc1_device_list.llc1_rwlock, RW_WRITER);
	}

	if (llc->llc_mcast) {
		for (i = 0; i < llc1_device_list.llc1_multisize; i++) {
			llc_mcast_t *mcast;

			if ((mcast = llc->llc_mcast[i]) != NULL) {
				/* disable from stream and possibly lower */
				llc1_send_disable_multi(llc->llc_mac_info,
				    mcast);
				llc->llc_mcast[i] = NULL;
			}
		}
		kmem_free(llc->llc_mcast,
		    sizeof (llc_mcast_t *) * llc->llc_multicnt);
		llc->llc_mcast = NULL;
	}
	if (llc->llc_mac_info)
		llc->llc_mac_info->llcp_nstreams--;
	llc->llc_sap = 0;
	llc->llc_state = DL_UNATTACHED;
	if (mp) {
		dlokack(q, mp, DL_DETACH_REQ);
	}
	return (LLCE_OK);
}

/*
 * llc1_enable_multi enables multicast address on the stream if the mac layer
 * isn't enabled for this address, enable at that level as well.
 */
static int
llc1_enable_multi(queue_t *q, mblk_t *mp)
{
	llc1_t *llc;
	llc_mac_info_t *macinfo;
	struct ether_addr *maddr;
	dl_enabmulti_req_t *multi;
	llc_mcast_t *mcast;
	int	status = DL_BADADDR;
	int	i;

#if defined(LLC1_DEBUG)
	if (llc1_debug & LLCPROT) {
		printf("llc1_enable_multi(%x, %x)\n", q, mp);
	}
#endif

	llc = (llc1_t *)q->q_ptr;

	if (llc->llc_state == DL_UNATTACHED)
		return (DL_OUTSTATE);

	macinfo = llc->llc_mac_info;
	multi = (dl_enabmulti_req_t *)mp->b_rptr;
	maddr = (struct ether_addr *)(mp->b_rptr + multi->dl_addr_offset);

	/*
	 * check to see if this multicast address is valid if it is, then
	 * check to see if it is already in the per stream table and the per
	 * device table if it is already in the per stream table, if it isn't
	 * in the per device, add it.  If it is, just set a pointer.  If it
	 * isn't, allocate what's necessary.
	 */

	if (MBLKL(mp) >= sizeof (dl_enabmulti_req_t) &&
	    MBLKIN(mp, multi->dl_addr_offset, multi->dl_addr_length) &&
	    multi->dl_addr_length == macinfo->llcp_addrlen &&
	    ismulticast(maddr->ether_addr_octet)) {
		/* request appears to be valid */
		/* does this address appear in current table? */
		if (llc->llc_mcast == NULL) {
			/* no mcast addresses -- allocate table */
			llc->llc_mcast =
			    GETSTRUCT(llc_mcast_t *,
			    llc1_device_list.llc1_multisize);
			if (llc->llc_mcast == NULL)
				return (DL_SYSERR);
			llc->llc_multicnt = llc1_device_list.llc1_multisize;
		} else {
			for (i = 0; i < llc1_device_list.llc1_multisize; i++) {
				if (llc->llc_mcast[i] &&
				    bcmp(llc->llc_mcast[i]->llcm_addr,
				    maddr->ether_addr_octet, ETHERADDRL)) {
					/* this is a match -- just succeed */
					dlokack(q, mp, DL_ENABMULTI_REQ);
					return (LLCE_OK);
				}
			}
		}
		/*
		 * there wasn't one so check to see if the mac layer has one
		 */
		if (macinfo->llcp_mcast == NULL) {
			macinfo->llcp_mcast =
			    GETSTRUCT(llc_mcast_t,
			    llc1_device_list.llc1_multisize);
			if (macinfo->llcp_mcast == NULL)
				return (DL_SYSERR);
		}
		for (mcast = NULL, i = 0;
		    i < llc1_device_list.llc1_multisize; i++) {
			if (macinfo->llcp_mcast[i].llcm_refcnt &&
			    bcmp(macinfo->llcp_mcast[i].llcm_addr,
			    maddr->ether_addr_octet, ETHERADDRL) == 0) {
				mcast = &macinfo->llcp_mcast[i];
				break;
			}
		}
		if (mcast == NULL) {
			mblk_t *nmp;

			nmp = dupmsg(mp);
			if (nmp) {
				nmp->b_cont = NULL;
				DB_TYPE(nmp) = M_PROTO;
				putnext(WR(macinfo->llcp_queue), nmp);
			}
			/* find an empty slot to fill in */
			for (mcast = macinfo->llcp_mcast, i = 0;
			    i < llc1_device_list.llc1_multisize; i++, mcast++) {
				if (mcast->llcm_refcnt == 0) {
					bcopy(maddr->ether_addr_octet,
					    mcast->llcm_addr, ETHERADDRL);
					break;
				}
			}
		}
		if (mcast != NULL) {
			for (i = 0; i < llc1_device_list.llc1_multisize; i++) {
				if (llc->llc_mcast[i] == NULL) {
					llc->llc_mcast[i] = mcast;
					mcast->llcm_refcnt++;
					dlokack(q, mp, DL_ENABMULTI_REQ);
					return (LLCE_OK);
				}
			}
		}
		status = DL_TOOMANY;
	}
	return (status);
}

/*
 * llc1_disable_multi disable the multicast address on the stream if last
 * reference for the mac layer, disable there as well
 */
static int
llc1_disable_multi(queue_t *q, mblk_t *mp)
{
	llc1_t *llc;
	llc_mac_info_t *macinfo;
	struct ether_addr *maddr;
	dl_enabmulti_req_t *multi;
	int	status = DL_BADADDR, i;
	llc_mcast_t *mcast;

#if defined(LLC1_DEBUG)
	if (llc1_debug & LLCPROT) {
		printf("llc1_enable_multi(%x, %x)\n", q, mp);
	}
#endif

	llc = (llc1_t *)q->q_ptr;

	if (llc->llc_state == DL_UNATTACHED)
		return (DL_OUTSTATE);

	macinfo = llc->llc_mac_info;
	multi = (dl_enabmulti_req_t *)mp->b_rptr;
	maddr = (struct ether_addr *)(multi + 1);

	if (MBLKL(mp) >= sizeof (dl_enabmulti_req_t) &&
	    MBLKIN(mp, multi->dl_addr_offset, multi->dl_addr_length)) {
		/* request appears to be valid */
		/* does this address appear in current table? */
		if (llc->llc_mcast != NULL) {
			for (i = 0; i < llc->llc_multicnt; i++)
				if (((mcast = llc->llc_mcast[i]) != NULL) &&
				    mcast->llcm_refcnt &&
				    bcmp(mcast->llcm_addr,
				    maddr->ether_addr_octet, ETHERADDRL) == 0) {
					llc1_send_disable_multi(macinfo,
					    mcast);
					llc->llc_mcast[i] = NULL;
					dlokack(q, mp, DL_DISABMULTI_REQ);
					return (LLCE_OK);
				}
			status = DL_NOTENAB;
		}
	}
	return (status);
}

/*
 * llc1_send_disable_multi(llc, macinfo, mcast) this function is used to
 * disable a multicast address if the reference count goes to zero. The
 * disable request will then be forwarded to the lower stream.
 */
static void
llc1_send_disable_multi(llc_mac_info_t *macinfo, llc_mcast_t *mcast)
{
	mblk_t *mp;
	dl_disabmulti_req_t *dis;

	if (mcast == NULL) {
		return;
	}
	if (macinfo == NULL || macinfo->llcp_queue == NULL) {
		return;
	}
	if (--mcast->llcm_refcnt > 0)
		return;

	mp = allocb(sizeof (dl_disabmulti_req_t) + ETHERADDRL, BPRI_MED);
	if (mp) {
		dis = (dl_disabmulti_req_t *)mp->b_rptr;
		mp->b_wptr =
		    mp->b_rptr + sizeof (dl_disabmulti_req_t) + ETHERADDRL;
		dis->dl_primitive = DL_DISABMULTI_REQ;
		dis->dl_addr_offset = sizeof (dl_disabmulti_req_t);
		dis->dl_addr_length = ETHERADDRL;
		bcopy(mcast->llcm_addr,
		    (mp->b_rptr + sizeof (dl_disabmulti_req_t)), ETHERADDRL);
		DB_TYPE(mp) = M_PROTO;
		putnext(WR(macinfo->llcp_queue), mp);
	}
}

/*
 * llc1_findminor(device) searches the per device class list of STREAMS for
 * the first minor number not used.  Note that we currently don't allocate
 * minor 0.
 */

static minor_t
llc1_findminor(llc1dev_t *device)
{
	llc1_t *next;
	minor_t	minor;

	ASSERT(device != NULL);
	for (minor = 1; minor <= MAXMIN32; minor++) {
		for (next = device->llc1_str_next;
		    next != NULL && next != (llc1_t *)&device->llc1_str_next;
		    next = next->llc_next) {
			if (minor == next->llc_minor)
				goto nextminor;
		}
		return (minor);
nextminor:
		/* don't need to do anything */
		;
	}
	/*NOTREACHED*/
	return (0);
}

/*
 * llc1_req_info(q) simply construct a DL_INFO_REQ to be sent to the lower
 * stream this is used to populate the macinfo structure.
 */
static int
llc1_req_info(queue_t *q)
{
	dl_info_req_t *info;
	mblk_t *mp;

	mp = allocb(DL_INFO_REQ_SIZE, BPRI_MED);
	if (mp == NULL)
		return (-1);
	DB_TYPE(mp) = M_PCPROTO;
	info = (dl_info_req_t *)mp->b_rptr;
	mp->b_wptr = mp->b_rptr + DL_INFO_REQ_SIZE;
	info->dl_primitive = DL_INFO_REQ;
	putnext(q, mp);
	return (0);
}

/*
 * llc1_req_raw(macinfo) request that the lower stream enter DLIOCRAW mode
 */
static void
llc1_req_raw(llc_mac_info_t *macinfo)
{
	mblk_t *mp;

	mp = mkiocb(DLIOCRAW);
	if (mp == NULL)
		return;

	macinfo->llcp_iocid = ((struct iocblk *)mp->b_rptr)->ioc_id;

	putnext(macinfo->llcp_queue, mp);
	macinfo->llcp_flags |= LLC1_RAW_WAIT;
}

/*
 * llc1_send_bindreq
 * if lower stream isn't bound, bind it to something appropriate
 */
static void
llc1_send_bindreq(llc_mac_info_t *macinfo)
{
	mblk_t *mp;
	dl_bind_req_t *bind;

	if (macinfo->llcp_sap >= 0xFF) {
		/* have to quite sometime if the world is failing */
		macinfo->llcp_sap &= ~(LLC1_BINDING|LLC1_AVAILABLE);
		return;
	}

	mp = allocb(sizeof (dl_bind_req_t), BPRI_MED);
	if (mp == NULL)
		return;

	bind = (dl_bind_req_t *)mp->b_rptr;
	mp->b_wptr = mp->b_rptr + sizeof (dl_bind_req_t);

	bind->dl_primitive = DL_BIND_REQ;
	bind->dl_sap = macinfo->llcp_sap += 2; /* starts at 2, inc by 2  */
	macinfo->llcp_flags |= LLC1_BINDING;
	bind->dl_max_conind = 0;
	bind->dl_service_mode = DL_CLDLS;
	bind->dl_conn_mgmt = 0;
	bind->dl_xidtest_flg = 0;
	putnext(macinfo->llcp_queue, mp);
}

/*
 * llc1_form_udata(lld, macinfo, mp) format a DL_UNITDATA_IND message to be
 * sent to the user
 */
static mblk_t *
llc1_form_udata(llc1_t *lld, llc_mac_info_t *macinfo, mblk_t *mp)
{
	mblk_t *udmp, *nmp;
	dl_unitdata_ind_t *udata;
	struct ether_header *hdr;
	struct llchdr *llchdr;
	struct snaphdr *snap;

	if (macinfo->llcp_flags & LLC1_USING_RAW) {
		hdr = (struct ether_header *)mp->b_rptr;
		llchdr = (struct llchdr *)(hdr + 1);

	    /* allocate the DL_UNITDATA_IND M_PROTO header */
		udmp = allocb(sizeof (dl_unitdata_ind_t) +
		    2 * (macinfo->llcp_addrlen + 5), BPRI_MED);
		if (udmp == NULL) {
		/* might as well discard since we can't go further */
		freemsg(mp);
		return (NULL);
		}
		udata = (dl_unitdata_ind_t *)udmp->b_rptr;
		udmp->b_wptr += sizeof (dl_unitdata_ind_t);

		nmp = dupmsg(mp);	/* make a copy for future streams */
		if (lld->llc_sap != LLC_NOVELL_SAP)
			mp->b_rptr += sizeof (struct ether_header) +
			    sizeof (struct llchdr);
		else
			mp->b_rptr += sizeof (struct ether_header);

		if (lld->llc_flags & LLC_SNAP) {
			mp->b_rptr += sizeof (struct snaphdr);
			snap = (struct snaphdr *)(llchdr + 1);
		}

		/*
		 * now setup the DL_UNITDATA_IND header
		 */
		DB_TYPE(udmp) = M_PROTO;
		udata->dl_primitive = DL_UNITDATA_IND;
		udata->dl_dest_addr_offset = sizeof (dl_unitdata_ind_t);
		bcopy(hdr->ether_dhost.ether_addr_octet,
		    LLCADDR(udata, udata->dl_dest_addr_offset)->llca_addr,
		    macinfo->llcp_addrlen);

		if (lld->llc_flags & LLC_SNAP) {
			udata->dl_dest_addr_length = macinfo->llcp_addrlen + 2;
			LLCSADDR(udata, udata->dl_dest_addr_offset)->llca_ssap =
			    ntohs(*(ushort_t *)snap->snap_type);
		} else {
			udata->dl_dest_addr_length = macinfo->llcp_addrlen + 1;
			LLCADDR(udata, udata->dl_dest_addr_offset)->llca_sap =
			    llchdr->llc_dsap;
		}
		udmp->b_wptr += udata->dl_dest_addr_length;
		udata->dl_src_addr_offset = udata->dl_dest_addr_length +
		    udata->dl_dest_addr_offset;
		bcopy(hdr->ether_shost.ether_addr_octet,
		    LLCADDR(udata, udata->dl_src_addr_offset)->llca_addr,
		    macinfo->llcp_addrlen);
		if (lld->llc_flags & LLC_SNAP) {
			udata->dl_src_addr_length = macinfo->llcp_addrlen + 2;
			LLCSADDR(udata, udata->dl_src_addr_offset)->llca_ssap =
			    ntohs(*(ushort_t *)snap->snap_type);
		} else {
			udata->dl_src_addr_length = macinfo->llcp_addrlen + 1;
			LLCADDR(udata, udata->dl_src_addr_offset)->llca_sap =
			    llchdr->llc_ssap;
		}
		udata->dl_group_address = hdr->ether_dhost.ether_addr_octet[0] &
		    0x1;
		udmp->b_wptr += udata->dl_src_addr_length;
		udmp->b_cont = mp;
	} else {
		dl_unitdata_ind_t *ud2;
		if (mp->b_cont == NULL) {
		return (mp);	/* we can't do anything */
		}
	    /* if we end up here, we only want to patch the existing M_PROTO */
		nmp = dupmsg(mp);	/* make a copy for future streams */
		udata = (dl_unitdata_ind_t *)(mp->b_rptr);
		udmp = allocb(MBLKL(mp) + 4, BPRI_MED);
		bcopy(mp->b_rptr, udmp->b_rptr, sizeof (dl_unitdata_ind_t));
		ud2 = (dl_unitdata_ind_t *)(udmp->b_rptr);
		udmp->b_wptr += sizeof (dl_unitdata_ind_t);
		bcopy((caddr_t)mp->b_rptr + udata->dl_dest_addr_offset,
		    udmp->b_wptr, macinfo->llcp_addrlen);
		ud2->dl_dest_addr_offset = sizeof (dl_unitdata_ind_t);
		ud2->dl_dest_addr_length = macinfo->llcp_addrlen + 1;
		udmp->b_wptr += ud2->dl_dest_addr_length;
		bcopy((caddr_t)udmp->b_rptr + udata->dl_src_addr_offset,
		    udmp->b_wptr, macinfo->llcp_addrlen);
		ud2->dl_src_addr_length = ud2->dl_dest_addr_length;
		udmp->b_wptr += ud2->dl_src_addr_length;
		udmp->b_cont = mp->b_cont;
		if (lld->llc_sap != LLC_NOVELL_SAP)
			mp->b_cont->b_rptr += sizeof (struct llchdr);
		freeb(mp);

		DB_TYPE(udmp) = M_PROTO;
		udata = (dl_unitdata_ind_t *)(mp->b_rptr);
		llchdr = (struct llchdr *)(mp->b_cont->b_rptr);
		LLCADDR(udata, udata->dl_dest_addr_offset)->llca_sap =
		    llchdr->llc_dsap;
		LLCADDR(udata, udata->dl_src_addr_offset)->llca_sap =
		    llchdr->llc_ssap;
	}
#ifdef LLC1_DEBUG
		if (llc1_debug & LLCRECV)
		printf("llc1_recv: queued message to %x (%d)\n",
		    lld->llc_qptr, lld->llc_minor);
#endif
	/* enqueue for the service routine to process */
	putnext(RD(lld->llc_qptr), udmp);
	mp = nmp;
	return (mp);
}

/*
 * llc1_xid_reply(macinfo, mp) automatic reply to an XID command
 */
static mblk_t *
llc1_xid_reply(llc_mac_info_t *macinfo, mblk_t *mp, int sap)
{
	mblk_t *nmp, *rmp;
	struct ether_header *hdr, *msgether;
	struct llchdr *llchdr;
	struct llchdr *msgllc;
	struct llchdr_xid *xid;

	if (DB_TYPE(mp) == M_DATA) {
		hdr = (struct ether_header *)mp->b_rptr;
		llchdr = (struct llchdr *)(hdr + 1);
	} else {
		if (mp->b_cont == NULL)
			return (mp);
		llchdr = (struct llchdr *)(mp->b_cont->b_rptr);
	}

	/* we only want to respond to commands to avoid response loops */
	if (llchdr->llc_ssap & LLC_RESPONSE)
		return (mp);

	nmp = allocb(msgdsize(mp) + LLC_XID_INFO_SIZE, BPRI_MED);
	if (nmp == NULL) {
		return (mp);
	}

	/*
	 * now construct the XID reply frame
	 */
	if (DB_TYPE(mp) == M_DATA) {
		msgether = (struct ether_header *)nmp->b_rptr;
		nmp->b_wptr += sizeof (struct ether_header);
		bcopy(hdr->ether_shost.ether_addr_octet,
		    msgether->ether_dhost.ether_addr_octet,
		    macinfo->llcp_addrlen);
		bcopy(macinfo->llcp_macaddr,
		    msgether->ether_shost.ether_addr_octet,
		    macinfo->llcp_addrlen);
		msgether->ether_type = htons(sizeof (struct llchdr_xid) +
		    sizeof (struct llchdr));
		rmp = nmp;
	} else {
		dl_unitdata_req_t *ud;
		dl_unitdata_ind_t *rud;
		rud = (dl_unitdata_ind_t *)mp->b_rptr;

		rmp = allocb(sizeof (dl_unitdata_req_t) +
		    macinfo->llcp_addrlen + 5, BPRI_MED);
		if (rmp == NULL)
			return (mp);

		DB_TYPE(rmp) = M_PROTO;
		bzero(rmp->b_rptr, sizeof (dl_unitdata_req_t));
		ud = (dl_unitdata_req_t *)rmp->b_rptr;
		ud->dl_primitive = DL_UNITDATA_REQ;
		ud->dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
		ud->dl_dest_addr_length = macinfo->llcp_addrlen + 1;

		rmp->b_wptr += sizeof (dl_unitdata_req_t);
		bcopy(LLCADDR(mp->b_rptr, rud->dl_src_addr_offset),
		    LLCADDR(rmp->b_rptr, ud->dl_dest_addr_offset),
		    macinfo->llcp_addrlen);
		LLCADDR(rmp->b_rptr, ud->dl_dest_addr_offset)->llca_sap =
		    LLCADDR(mp->b_rptr, rud->dl_src_addr_offset)->llca_sap;
		rmp->b_wptr += sizeof (struct llcaddr);
		rmp->b_cont = nmp;
	}

	msgllc = (struct llchdr *)nmp->b_wptr;
	xid = (struct llchdr_xid *)(msgllc + 1);
	nmp->b_wptr += sizeof (struct llchdr);

	msgllc->llc_dsap = llchdr->llc_ssap;

	/* mark it a response */
	msgllc->llc_ssap = sap | LLC_RESPONSE;

	msgllc->llc_ctl = llchdr->llc_ctl;
	xid->llcx_format = LLC_XID_FMTID;
	xid->llcx_class = LLC_XID_TYPE_1;
	xid->llcx_window = 0;	/* we don't have connections yet */

	nmp->b_wptr += sizeof (struct llchdr_xid);
	macinfo->llcp_stats.llcs_xidxmt++;
	putnext(WR(macinfo->llcp_queue), rmp);
	return (mp);
}

/*
 * llc1_xid_ind_con(lld, macinfo, mp) form a DL_XID_IND or DL_XID_CON message
 * to send to the user since it was requested that the user process these
 * messages
 */
static mblk_t *
llc1_xid_ind_con(llc1_t *lld, llc_mac_info_t *macinfo, mblk_t *mp)
{
	mblk_t *nmp;
	dl_xid_ind_t *xid;
	struct ether_header *hdr;
	struct llchdr *llchdr;
	int raw;

	nmp = allocb(sizeof (dl_xid_ind_t) + 2 * (macinfo->llcp_addrlen + 1),
	    BPRI_MED);
	if (nmp == NULL)
		return (mp);

	if ((raw = (DB_TYPE(mp) == M_DATA)) != 0) {
		hdr = (struct ether_header *)mp->b_rptr;
		llchdr = (struct llchdr *)(hdr + 1);
	} else {
		if (mp->b_rptr == NULL)
			return (mp);
		llchdr = (struct llchdr *)mp->b_cont->b_rptr;
	}

	xid = (dl_xid_ind_t *)nmp->b_rptr;
	xid->dl_flag = (llchdr->llc_ctl & LLC_P) ? DL_POLL_FINAL : 0;
	xid->dl_dest_addr_offset = sizeof (dl_xid_ind_t);
	xid->dl_dest_addr_length = macinfo->llcp_addrlen + 1;

	if (raw) {
		bcopy(hdr->ether_dhost.ether_addr_octet,
		    (nmp->b_rptr + xid->dl_dest_addr_offset),
		    xid->dl_dest_addr_length);
	} else {
		dl_unitdata_ind_t *ind;
		ind = (dl_unitdata_ind_t *)mp->b_rptr;
		bcopy(LLCADDR(ind, ind->dl_dest_addr_offset),
		    (nmp->b_rptr + xid->dl_dest_addr_offset),
		    xid->dl_dest_addr_length);
	}

	LLCADDR(xid, xid->dl_dest_addr_offset)->llca_sap =
	    llchdr->llc_dsap;

	xid->dl_src_addr_offset =
	    xid->dl_dest_addr_offset + xid->dl_dest_addr_length;
	xid->dl_src_addr_length = xid->dl_dest_addr_length;

	if (raw) {
		bcopy(hdr->ether_shost.ether_addr_octet,
		    (nmp->b_rptr + xid->dl_src_addr_offset),
		    xid->dl_src_addr_length);
	} else {
		dl_unitdata_ind_t *ind;
		ind = (dl_unitdata_ind_t *)mp->b_rptr;
		bcopy(LLCADDR(mp->b_rptr, ind->dl_src_addr_offset),
		    (nmp->b_rptr + xid->dl_src_addr_offset),
		    ind->dl_src_addr_length);
	}
	LLCADDR(nmp->b_rptr, xid->dl_src_addr_offset)->llca_sap =
	    llchdr->llc_ssap & ~LLC_RESPONSE;

	nmp->b_wptr = nmp->b_rptr + sizeof (dl_xid_ind_t) +
	    2 * xid->dl_dest_addr_length;

	if (!(llchdr->llc_ssap & LLC_RESPONSE)) {
		xid->dl_primitive = DL_XID_IND;
	} else {
		xid->dl_primitive = DL_XID_CON;
	}

	DB_TYPE(nmp) = M_PROTO;
	if (raw) {
		if (MBLKL(mp) >
		    (sizeof (struct ether_header) + sizeof (struct llchdr))) {
			nmp->b_cont = dupmsg(mp);
			if (nmp->b_cont) {
				nmp->b_cont->b_rptr +=
					sizeof (struct ether_header) +
					sizeof (struct llchdr);
			}
		}
	} else if (mp->b_cont != NULL && MBLKL(mp->b_cont) >
						sizeof (struct llchdr)) {
		nmp->b_cont = dupmsg(mp->b_cont);
		(void) adjmsg(nmp->b_cont, sizeof (struct llchdr));
	}
	putnext(RD(lld->llc_qptr), nmp);
	return (mp);
}

/*
 * llc1_xid_req_res(q, mp, req_or_res) the user wants to send an XID message
 * or response construct a proper message and put on the net
 */
static int
llc1_xid_req_res(queue_t *q, mblk_t *mp, int req_or_res)
{
	dl_xid_req_t *xid = (dl_xid_req_t *)mp->b_rptr;
	llc1_t *llc = (llc1_t *)q->q_ptr;
	llc_mac_info_t *macinfo;
	mblk_t *nmp, *rmp;
	struct ether_header *hdr;
	struct llchdr *llchdr;

	if (llc == NULL || llc->llc_state == DL_UNATTACHED)
		return (DL_OUTSTATE);

	if (llc->llc_sap == LLC_NOVELL_SAP)
		return (DL_NOTSUPPORTED);

	if (llc->llc_flags & DL_AUTO_XID)
		return (DL_XIDAUTO);

	macinfo = llc->llc_mac_info;
	if (MBLKL(mp) < sizeof (dl_xid_req_t) ||
	    !MBLKIN(mp, xid->dl_dest_addr_offset, xid->dl_dest_addr_length)) {
		return (DL_BADPRIM);
	}

	nmp = allocb(sizeof (struct ether_header) + sizeof (struct llchdr) +
	    sizeof (struct llchdr_xid), BPRI_MED);

	if (nmp == NULL)
		return (LLCE_NOBUFFER);

	if (macinfo->llcp_flags & LLC1_USING_RAW) {
		hdr = (struct ether_header *)nmp->b_rptr;
		bcopy(LLCADDR(xid, xid->dl_dest_addr_offset)->llca_addr,
		    hdr->ether_dhost.ether_addr_octet, ETHERADDRL);
		bcopy(macinfo->llcp_macaddr,
		    hdr->ether_shost.ether_addr_octet, ETHERADDRL);
		hdr->ether_type = htons(sizeof (struct llchdr) + msgdsize(mp));
		nmp->b_wptr = nmp->b_rptr +
		    sizeof (struct ether_header) + sizeof (struct llchdr);
		llchdr = (struct llchdr *)(hdr + 1);
		rmp = nmp;
	} else {
		dl_unitdata_req_t *ud;
		rmp = allocb(sizeof (dl_unitdata_req_t) +
		    (macinfo->llcp_addrlen + 2), BPRI_MED);
		if (rmp == NULL) {
			freemsg(nmp);
			return (LLCE_NOBUFFER);
		}
		ud = (dl_unitdata_req_t *)rmp->b_rptr;
		DB_TYPE(rmp) = M_PROTO;
		ud->dl_primitive = DL_UNITDATA_REQ;
		ud->dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
		ud->dl_dest_addr_length = xid->dl_dest_addr_length;
		rmp->b_wptr += sizeof (dl_unitdata_req_t);
		bcopy(LLCADDR(xid, xid->dl_dest_addr_offset)->llca_addr,
		    LLCADDR(ud, ud->dl_dest_addr_offset),
		    xid->dl_dest_addr_length);
		LLCSADDR(ud, ud->dl_dest_addr_offset)->llca_ssap =
		    msgdsize(mp);
		rmp->b_wptr += xid->dl_dest_addr_length;
		rmp->b_cont = nmp;
		llchdr = (struct llchdr *)nmp->b_rptr;
		nmp->b_wptr += sizeof (struct llchdr);
	}

	llchdr->llc_dsap = LLCADDR(xid, xid->dl_dest_addr_offset)->llca_sap;
	llchdr->llc_ssap = llc->llc_sap | (req_or_res ? LLC_RESPONSE : 0);
	llchdr->llc_ctl =
	    LLC_XID | ((xid->dl_flag & DL_POLL_FINAL) ? LLC_P : 0);

	nmp->b_cont = mp->b_cont;
	mp->b_cont = NULL;
	freeb(mp);
	macinfo->llcp_stats.llcs_xidxmt++;
	putnext(WR(macinfo->llcp_queue), rmp);
	return (LLCE_OK);
}

/*
 * llc1_test_reply(macinfo, mp)
 * automatic reply to a TEST message
 */
static mblk_t *
llc1_test_reply(llc_mac_info_t *macinfo, mblk_t *mp, int sap)
{
	mblk_t *nmp;
	struct ether_header *hdr, *msgether;
	struct llchdr *llchdr;
	struct llchdr *msgllc;
	int poll_final;

	if (DB_TYPE(mp) == M_PROTO) {
		if (mp->b_cont == NULL)
			return (mp);
		llchdr = (struct llchdr *)mp->b_cont->b_rptr;
		hdr = NULL;
	} else {
		hdr = (struct ether_header *)mp->b_rptr;
		llchdr = (struct llchdr *)(hdr + 1);
	}

	/* we only want to respond to commands to avoid response loops */
	if (llchdr->llc_ssap & LLC_RESPONSE)
		return (mp);

	nmp = copymsg(mp);	/* so info field is duplicated */
	if (nmp == NULL) {
		nmp = mp;
		mp = NULL;
	}
	/*
	 * now construct the TEST reply frame
	 */


	poll_final = llchdr->llc_ctl & LLC_P;

	if (DB_TYPE(nmp) == M_PROTO) {
		dl_unitdata_req_t *udr = (dl_unitdata_req_t *)nmp->b_rptr;
		dl_unitdata_ind_t *udi = (dl_unitdata_ind_t *)nmp->b_rptr;

		/* make into a request */
		udr->dl_primitive = DL_UNITDATA_REQ;
		udr->dl_dest_addr_offset = udi->dl_src_addr_offset;
		udr->dl_dest_addr_length = udi->dl_src_addr_length;
		udr->dl_priority.dl_min = udr->dl_priority.dl_max = 0;
		msgllc = (struct llchdr *)nmp->b_cont->b_rptr;
	} else {
		msgether = (struct ether_header *)nmp->b_rptr;
		bcopy(hdr->ether_shost.ether_addr_octet,
		    msgether->ether_dhost.ether_addr_octet,
		    macinfo->llcp_addrlen);
		bcopy(macinfo->llcp_macaddr,
		    msgether->ether_shost.ether_addr_octet,
		    macinfo->llcp_addrlen);
		msgllc = (struct llchdr *)(msgether+1);
	}

	msgllc->llc_dsap = llchdr->llc_ssap;

	/* mark it as a response */
	msgllc->llc_ssap = sap |  LLC_RESPONSE;
	msgllc->llc_ctl = LLC_TEST | poll_final;

	macinfo->llcp_stats.llcs_testxmt++;
	putnext(WR(macinfo->llcp_queue), nmp);
	return (mp);
}

/*
 * llc1_test_ind_con(lld, macinfo, mp) form a DL_TEST_IND or DL_TEST_CON
 * message to send to the user since it was requested that the user process
 * these messages
 */
static mblk_t *
llc1_test_ind_con(llc1_t *lld, llc_mac_info_t *macinfo, mblk_t *mp)
{
	mblk_t *nmp;
	dl_test_ind_t *test;
	struct ether_header *hdr;
	struct llchdr *llchdr;
	int raw;

	nmp = allocb(sizeof (dl_test_ind_t) + 2 * (ETHERADDRL + 1), BPRI_MED);
	if (nmp == NULL)
		return (NULL);

	if ((raw = (DB_TYPE(mp) == M_DATA)) != 0) {
		hdr = (struct ether_header *)mp->b_rptr;
		llchdr = (struct llchdr *)(hdr + 1);
	} else {
		if (mp->b_rptr == NULL)
			return (mp);
		llchdr = (struct llchdr *)mp->b_cont->b_rptr;
	}

	test = (dl_test_ind_t *)nmp->b_rptr;
	test->dl_flag = (llchdr->llc_ctl & LLC_P) ? DL_POLL_FINAL : 0;
	test->dl_dest_addr_offset = sizeof (dl_test_ind_t);
	test->dl_dest_addr_length = macinfo->llcp_addrlen + 1;

	if (raw) {
		bcopy(hdr->ether_dhost.ether_addr_octet,
		    LLCADDR(nmp->b_rptr, test->dl_dest_addr_offset)->llca_addr,
		    test->dl_dest_addr_length);
	} else {
		dl_unitdata_ind_t *ind;
		ind = (dl_unitdata_ind_t *)mp->b_rptr;
		bcopy(LLCADDR(ind, ind->dl_dest_addr_offset),
		    (nmp->b_rptr + test->dl_dest_addr_offset),
		    test->dl_dest_addr_length);
	}

	LLCADDR(test, test->dl_dest_addr_offset)->llca_sap =
	    llchdr->llc_dsap;

	test->dl_src_addr_offset = test->dl_dest_addr_offset +
	    test->dl_dest_addr_length;
	test->dl_src_addr_length = test->dl_dest_addr_length;

	if (raw) {
		bcopy(hdr->ether_shost.ether_addr_octet,
		    LLCADDR(nmp->b_rptr, test->dl_src_addr_offset)->llca_addr,
		    test->dl_src_addr_length);
	} else {
		dl_unitdata_ind_t *ind;
		ind = (dl_unitdata_ind_t *)mp->b_rptr;
		bcopy(LLCADDR(mp->b_rptr, ind->dl_src_addr_offset),
		    (nmp->b_rptr + test->dl_src_addr_offset),
		    ind->dl_src_addr_length);
	}
	LLCADDR(nmp->b_rptr, test->dl_src_addr_offset)->llca_sap =
	    llchdr->llc_ssap & ~LLC_RESPONSE;

	nmp->b_wptr = nmp->b_rptr + sizeof (dl_test_ind_t) +
	    2 * test->dl_dest_addr_length;

	if (!(llchdr->llc_ssap & LLC_RESPONSE)) {
		test->dl_primitive = DL_TEST_IND;
	} else {
		test->dl_primitive = DL_TEST_CON;
	}

	DB_TYPE(nmp) = M_PROTO;
	if (raw) {
		if (MBLKL(mp) >
		    (sizeof (struct ether_header) + sizeof (struct llchdr))) {
			nmp->b_cont = dupmsg(mp);
			if (nmp->b_cont) {
				nmp->b_cont->b_rptr +=
					sizeof (struct ether_header) +
					sizeof (struct llchdr);
			}
		}
	} else if (mp->b_cont != NULL && MBLKL(mp->b_cont) >
					sizeof (struct llchdr)) {
		nmp->b_cont = dupmsg(mp->b_cont);
		(void) adjmsg(nmp->b_cont, sizeof (struct llchdr));
	}
	putnext(RD(lld->llc_qptr), nmp);
	return (mp);
}

/*
 * llc1_test_req_res(q, mp, req_or_res) the user wants to send a TEST
 * message or response construct a proper message and put on the net
 */
static int
llc1_test_req_res(queue_t *q, mblk_t *mp, int req_or_res)
{
	dl_test_req_t *test = (dl_test_req_t *)mp->b_rptr;
	llc1_t *llc = (llc1_t *)q->q_ptr;
	llc_mac_info_t *macinfo;
	mblk_t *nmp, *rmp;
	struct ether_header *hdr;
	struct llchdr *llchdr;

	if (llc == NULL || llc->llc_state == DL_UNATTACHED)
		return (DL_OUTSTATE);

	if (llc->llc_sap == LLC_NOVELL_SAP)
		return (DL_NOTSUPPORTED);

	if (llc->llc_flags & DL_AUTO_TEST)
		return (DL_TESTAUTO);

	macinfo = llc->llc_mac_info;
	if (MBLKL(mp) < sizeof (dl_test_req_t) ||
	    !MBLKIN(mp, test->dl_dest_addr_offset,
	    test->dl_dest_addr_length)) {
		return (DL_BADPRIM);
	}

	nmp = allocb(sizeof (struct ether_header) + sizeof (struct llchdr),
	    BPRI_MED);

	if (nmp == NULL)
		return (LLCE_NOBUFFER);

	if (macinfo->llcp_flags & LLC1_USING_RAW) {
		hdr = (struct ether_header *)nmp->b_rptr;
		bcopy(LLCADDR(test, test->dl_dest_addr_offset)->llca_addr,
		    hdr->ether_dhost.ether_addr_octet, ETHERADDRL);
		bcopy(macinfo->llcp_macaddr,
		    hdr->ether_shost.ether_addr_octet, ETHERADDRL);
		hdr->ether_type = htons(sizeof (struct llchdr) + msgdsize(mp));
		nmp->b_wptr = nmp->b_rptr +
		    sizeof (struct ether_header) + sizeof (struct llchdr);
		llchdr = (struct llchdr *)(hdr + 1);
		rmp = nmp;
	} else {
		dl_unitdata_req_t *ud;

		rmp = allocb(sizeof (dl_unitdata_req_t) +
		    (macinfo->llcp_addrlen + 2), BPRI_MED);
		if (rmp == NULL) {
			freemsg(nmp);
			return (LLCE_NOBUFFER);

		}
		ud = (dl_unitdata_req_t *)rmp->b_rptr;
		DB_TYPE(rmp) = M_PROTO;
		ud->dl_primitive = DL_UNITDATA_REQ;
		ud->dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
		ud->dl_dest_addr_length = test->dl_dest_addr_length;
		rmp->b_wptr += sizeof (dl_unitdata_req_t);
		bcopy(LLCADDR(test, test->dl_dest_addr_offset)->llca_addr,
		    LLCADDR(ud, ud->dl_dest_addr_offset),
		    test->dl_dest_addr_length);
		LLCSADDR(ud, ud->dl_dest_addr_offset)->llca_ssap =
		    msgdsize(mp);
		rmp->b_wptr += test->dl_dest_addr_length;
		rmp->b_cont = nmp;
		llchdr = (struct llchdr *)nmp->b_rptr;
		nmp->b_wptr += sizeof (struct llchdr);
	}

	llchdr->llc_dsap = LLCADDR(test, test->dl_dest_addr_offset)->llca_sap;
	llchdr->llc_ssap = llc->llc_sap | (req_or_res ? LLC_RESPONSE : 0);
	llchdr->llc_ctl =
	    LLC_TEST | ((test->dl_flag & DL_POLL_FINAL) ? LLC_P : 0);

	nmp->b_cont = mp->b_cont;
	mp->b_cont = NULL;
	freeb(mp);
	macinfo->llcp_stats.llcs_testxmt++;
	putnext(WR(macinfo->llcp_queue), rmp);
	return (LLCE_OK);
}

/*
 * llc1_find_waiting(macinfo, mp, prim) look for a stream waiting for a
 * response to a message identified by prim and send it to the user.
 */
static void
llc1_find_waiting(llc_mac_info_t *macinfo, mblk_t *mp, long prim)
{
	llc1_t *llc;

	for (llc = llc1_device_list.llc1_str_next;
	    llc != (llc1_t *)&llc1_device_list.llc1_str_next;
	    llc = llc->llc_next)
		if (llc->llc_mac_info == macinfo &&
		    prim == llc->llc_waiting_for) {
			putnext(RD(llc->llc_qptr), mp);
			llc->llc_waiting_for = -1;
			return;
		}
	freemsg(mp);
}

static void
llc1insque(void *elem, void *pred)
{
	struct qelem *pelem = elem;
	struct qelem *ppred = pred;
	struct qelem *pnext = ppred->q_forw;

	pelem->q_forw = pnext;
	pelem->q_back = ppred;
	ppred->q_forw = pelem;
	pnext->q_back = pelem;
}

static void
llc1remque(void *arg)
{
	struct qelem *pelem = arg;
	struct qelem *elem = arg;

	ASSERT(pelem->q_forw != NULL);
	pelem->q_forw->q_back = pelem->q_back;
	pelem->q_back->q_forw = pelem->q_forw;
	elem->q_back = elem->q_forw = NULL;
}

/* VARARGS */
static void
llc1error(dip, fmt, a1, a2, a3, a4, a5, a6)
	dev_info_t *dip;
	char   *fmt, *a1, *a2, *a3, *a4, *a5, *a6;
{
	static long last;
	static char *lastfmt;
	time_t now;

	/*
	 * Don't print same error message too often.
	 */
	now = gethrestime_sec();
	if ((last == (now & ~1)) && (lastfmt == fmt))
		return;
	last = now & ~1;
	lastfmt = fmt;

	cmn_err(CE_CONT, "%s%d:  ",
		ddi_get_name(dip), ddi_get_instance(dip));
	cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5, a6);
	cmn_err(CE_CONT, "\n");
}

/*ARGSUSED1*/
static int
llc1_update_kstat(kstat_t *ksp, int rw)
{
	llc_mac_info_t *macinfo;
	kstat_named_t *kstat;
	struct llc_stats *stats;

	if (ksp == NULL)
		return (0);

	kstat = (kstat_named_t *)(ksp->ks_data);
	macinfo = (llc_mac_info_t *)(ksp->ks_private);
	stats = &macinfo->llcp_stats;

	kstat[LLCS_NOBUFFER].value.ul = stats->llcs_nobuffer;
	kstat[LLCS_MULTIXMT].value.ul = stats->llcs_multixmt;
	kstat[LLCS_MULTIRCV].value.ul = stats->llcs_multircv;
	kstat[LLCS_BRDCSTXMT].value.ul = stats->llcs_brdcstxmt;
	kstat[LLCS_BRDCSTRCV].value.ul = stats->llcs_brdcstrcv;
	kstat[LLCS_BLOCKED].value.ul = stats->llcs_blocked;
	kstat[LLCS_PKTXMT].value.ul = stats->llcs_pktxmt;
	kstat[LLCS_PKTRCV].value.ul = stats->llcs_pktrcv;
	kstat[LLCS_BYTEXMT].value.ul = stats->llcs_bytexmt;
	kstat[LLCS_BYTERCV].value.ul = stats->llcs_bytercv;
	kstat[LLCS_XIDXMT].value.ul = stats->llcs_xidxmt;
	kstat[LLCS_XIDRCV].value.ul = stats->llcs_xidrcv;
	kstat[LLCS_TESTXMT].value.ul = stats->llcs_testxmt;
	kstat[LLCS_TESTRCV].value.ul = stats->llcs_testrcv;
	kstat[LLCS_IERRORS].value.ul = stats->llcs_ierrors;
	kstat[LLCS_OERRORS].value.ul = stats->llcs_oerrors;
	return (0);
}

static void
llc1_init_kstat(llc_mac_info_t *macinfo)
{
	kstat_named_t *ksp;

	/*
	 * Note that the temporary macinfo->llcp_ppa number is negative.
	 */
	macinfo->llcp_kstatp = kstat_create("llc", (-macinfo->llcp_ppa - 1),
	    NULL, "net", KSTAT_TYPE_NAMED,
	    sizeof (struct llc_stats) / sizeof (long), 0);
	if (macinfo->llcp_kstatp == NULL)
		return;

	macinfo->llcp_kstatp->ks_update = llc1_update_kstat;
	macinfo->llcp_kstatp->ks_private = (void *)macinfo;

	ksp = (kstat_named_t *)(macinfo->llcp_kstatp->ks_data);

	kstat_named_init(&ksp[LLCS_NOBUFFER], "nobuffer", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_MULTIXMT], "multixmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_MULTIRCV], "multircv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_BRDCSTXMT], "brdcstxmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_BRDCSTRCV], "brdcstrcv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_BLOCKED], "blocked", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_PKTXMT], "pktxmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_PKTRCV], "pktrcv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_BYTEXMT], "bytexmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_BYTERCV], "bytercv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_XIDXMT], "xidxmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_XIDRCV], "xidrcv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_TESTXMT], "testxmt", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_TESTRCV], "testrcv", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_IERRORS], "ierrors", KSTAT_DATA_ULONG);
	kstat_named_init(&ksp[LLCS_OERRORS], "oerrors", KSTAT_DATA_ULONG);
	kstat_install(macinfo->llcp_kstatp);
}

static void
llc1_uninit_kstat(llc_mac_info_t *macinfo)
{
	if (macinfo->llcp_kstatp) {
		kstat_delete(macinfo->llcp_kstatp);
		macinfo->llcp_kstatp = NULL;
	}
}

/*
 * llc1_subs_bind(q, mp)
 *	implements the DL_SUBS_BIND_REQ primitive
 *	this only works for a STREAM bound to LLC_SNAP_SAP
 *	or one bound to the automatic SNAP mode.
 *	If bound to LLC_SNAP_SAP, the subs bind can be:
 *	- 2 octets treated as a native byte order short (ethertype)
 *	- 3 octets treated as a network order byte string (OID part)
 *	- 5 octets treated as a network order byte string (full SNAP header)
 *	If bound to an automatic SNAP mode sap, then only the 3 octet
 *	form is allowed
 */
static int
llc1_subs_bind(queue_t *q, mblk_t *mp)
{
	llc1_t *lld = (llc1_t *)q->q_ptr;
	dl_subs_bind_req_t *subs = (dl_subs_bind_req_t *)mp->b_rptr;
	ushort_t subssap;
	uchar_t *sapstr;
	int result;


#if defined(LLC1_DEBUG)
	if (llc1_debug & (LLCTRACE|LLCPROT)) {
			printf("llc1_subs_bind (%x, %x)\n", q, mp);
	}
#endif

	if (lld == NULL || lld->llc_state != DL_IDLE) {
		result = DL_OUTSTATE;
	} else if (lld->llc_sap != LLC_SNAP_SAP ||
	    subs->dl_subs_bind_class != DL_HIERARCHICAL_BIND) {
		/* we only want to support this for SNAP at present */
		result = DL_UNSUPPORTED;
	} else {

		lld->llc_state = DL_SUBS_BIND_PND;

		sapstr = (uchar_t *)(mp->b_rptr + subs->dl_subs_sap_offset);

		result = LLCE_OK;
		switch (subs->dl_subs_sap_length) {
		case 2:		/* just the ethertype part */
			if (lld->llc_flags & LLC_SNAP) {
				result = DL_BADADDR;
				break;
			}
			((uchar_t *)&subssap)[0] = sapstr[0];
			((uchar_t *)&subssap)[1] = sapstr[1];
			subssap = htons(subssap);
			lld->llc_snap[3] = ((uchar_t *)&subssap)[0];
			lld->llc_snap[4] = ((uchar_t *)&subssap)[1];
			lld->llc_flags |= LLC_SNAP;
			break;

		case 3:		/* just the OID part */
			if ((lld->llc_flags & (LLC_SNAP|LLC_SNAP_OID)) ==
			    (LLC_SNAP|LLC_SNAP_OID)) {
				result = DL_BADADDR;
				break;
			}
			bcopy(sapstr, lld->llc_snap, 3);
			lld->llc_flags |= LLC_SNAP_OID;
			break;

		case 5:		/* full SNAP header */
			if (lld->llc_flags & (LLC_SNAP|LLC_SNAP_OID)) {
				result = DL_BADADDR;
				break;
			}
			bcopy(sapstr, lld->llc_snap, 5);
			lld->llc_flags |= LLC_SNAP|LLC_SNAP_OID;
			break;
		}
		/* if successful, acknowledge and enter the proper state */
		if (result == LLCE_OK) {
			mblk_t *nmp = mp;
			dl_subs_bind_ack_t *ack;

			if (DB_REF(mp) != 1 ||
			    MBLKL(mp) < (sizeof (dl_subs_bind_ack_t) + 5)) {
				freemsg(mp);
				nmp = allocb(sizeof (dl_subs_bind_ack_t) + 5,
				    BPRI_MED);
			}
			ack = (dl_subs_bind_ack_t *)nmp->b_rptr;
			nmp->b_wptr = nmp->b_rptr +
			    sizeof (dl_subs_bind_ack_t) + 5;
			ack->dl_primitive = DL_SUBS_BIND_ACK;
			ack->dl_subs_sap_offset = sizeof (dl_subs_bind_ack_t);
			ack->dl_subs_sap_length = 5;
			bcopy(lld->llc_snap,
			    (caddr_t)nmp->b_rptr + ack->dl_subs_sap_offset + 5,
			    5);
			DB_TYPE(nmp) = M_PCPROTO;
			qreply(q, nmp);

		}
		lld->llc_state = DL_IDLE;
	}
	return (result);
}

/*
 *
 */
static int
llc1_subs_unbind(void)
{
	return (DL_UNSUPPORTED);
}

char *
snapdmp(uchar_t *bstr)
{
	static char buff[32];

	(void) sprintf(buff, "%x.%x.%x.%x.%x",
	    bstr[0],
	    bstr[1],
	    bstr[2],
	    bstr[3],
	    bstr[4]);
	return (buff);
}

static int
llc1_snap_match(llc1_t *lld, struct snaphdr *snap)
{
	return (bcmp(snap->snap_oid, lld->llc_snap, 5) == 0);
}