/*
* 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);
}