OpenSS7
SS7 for the
Common Man
© Copyright 1997-2007 OpenSS7 Corporation All Rights Reserved.
Last modified: Sat, 16 Aug 2008 01:55:14 GMT
Home TopIndex FirstPrev Next LastMore Download Info FAQ Mail  Home -> Resources -> Browse Source -> /code/strxns/src/drivers/ldl.c
Quick Links

Download

SCTP

SIGTRAN

SS7

Hardware

STREAMS

Asterisk

Related

Package

Manual

FAQ

Browse Source

Applications

SS7 Stack

ISDN Stack

SIGTRAN Stack

VoIP Stack

MG Stack

SS7/ISDN Devices

IP Transport

Embedded Systems

Operating System

Resources

Packages

Sys Req

Download

Mailing Lists

Browse Source

CVS Archive

Bug Reports

Library

Hardware

Vendor Links

Home

Overview

Status

Documentation

Resources

About

News

Description: Code

File /code/strxns/src/drivers/ldl.c



#ident "@(#) ldl.c,v openss7-0_9_2_F(0.9.2.37) 2007/04/02 12:01:47"

static char const ident[] =
    "ldl.c,v openss7-0_9_2_F(0.9.2.37) 2007/04/02 12:01:47";

#define _SVR4_SOURCE
#define _LIS_SOURCE

#include <sys/os7/compat.h>

#include <linux/bitops.h>

#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <net/pkt_sched.h>

#include <sys/dlpi.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
#define	KERNEL_2_0
#else
#define	KERNEL_2_1
# if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,0)
# define KERNEL_2_3
# endif
#endif

#include <sys/ldl.h>

#undef WITH_32BIT_CONVERSION
#if defined LFS && defined __LP64__
#define WITH_32BIT_CONVERSION 1
#endif

#define LDL_DESCRIP	"UNIX SYSTEM V RELEASE 4.2 FAST STREAMS FOR LINUX"
#define LDL_EXTRA	"Part of the OpenSS7 Stack for Linux Fast-STREAMS."
#define LDL_COPYRIGHT	"Copyright (c) 1997-2006 OpenSS7 Corporation. All Rights Reserved."
#define LDL_REVISION	"LfS ldl.c,v openss7-0_9_2_F (0.9.2.37) 2007/04/02 12:01:47"
#define LDL_DEVICE	"SVR 4.2 STREAMS INET DLPI Drivers (NET4)"
#define LDL_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define LDL_LICENSE	"GPL"
#define LDL_BANNER	LDL_DESCRIP	"\n" \
			LDL_EXTRA	"\n" \
			LDL_REVISION	"\n" \
			LDL_COPYRIGHT	"\n" \
			LDL_DEVICE	"\n" \
			LDL_CONTACT
#define LDL_SPLASH	LDL_DEVICE	"\n" \
			LDL_REVISION

#ifdef LINUX
MODULE_AUTHOR(LDL_CONTACT);
MODULE_DESCRIPTION(LDL_DESCRIP);
MODULE_SUPPORTED_DEVICE(LDL_DEVICE);
#ifdef MODULE_LICENSE
MODULE_LICENSE(LDL_LICENSE);
#endif
#if defined MODULE_ALIAS
MODULE_ALIAS("streams-ldl");
MODULE_ALIAS("streams-link-driver");
MODULE_ALIAS("streams-link-dri");
#endif
#endif

#ifdef LFS
#define LDL_DRV_ID	CONFIG_STREAMS_LDL_MODID
#define LDL_DRV_NAME	CONFIG_STREAMS_LDL_NAME
#define LDL_CMAJORS	CONFIG_STREAMS_LDL_NMAJORS
#define LDL_CMAJOR_0	CONFIG_STREAMS_LDL_MAJOR
#define LDL_UNITS	CONFIG_STREAMS_LDL_NMINORS
#endif

#ifdef LINUX
#ifdef MODULE_ALIAS
#ifdef LFS
MODULE_ALIAS("streams-modid-" __stringify(CONFIG_STREAMS_LDL_MODID));
MODULE_ALIAS("streams-driver-link-driver");
MODULE_ALIAS("streams-driver-link-dri");
MODULE_ALIAS("streams-major-" __stringify(CONFIG_STREAMS_LDL_MAJOR));
MODULE_ALIAS("/dev/streams/link-dri");
MODULE_ALIAS("/dev/streams/link-dri
MODULE_ALIAS("char-major-" __stringify(LDL_CMAJOR_0));
MODULE_ALIAS("char-major-" __stringify(LDL_CMAJOR_0) "-*");
MODULE_ALIAS("char-major-" __stringify(LDL_CMAJOR_0) "-0");
MODULE_ALIAS("/dev/ldl");
#endif
#endif

#define DRV_ID		LDL_DRV_ID
#define DRV_NAME	LDL_DRV_NAME
#define CMAJORS		LDL_CMAJORS
#define CMAJOR_0	LDL_CMAJOR_0
#define UNITS		LDL_UNITS

#ifdef MODULE
#define DRV_BANNER	LDL_BANNER
#else
#define DRV_BANNER	LDL_SPLASH
#endif

STATIC struct module_info ldl_minfo = {
	.mi_idnum = DRV_ID,
	.mi_idname = DRV_NAME,
	.mi_minpsz = 4,
	.mi_maxpsz = INFPSZ,
	.mi_hiwat = 0x10000,
	.mi_lowat = 0x04000,
};

STATIC struct module_stat ldl_rdmstat = { };
STATIC struct module_stat ldl_wrmstat = { };

STATIC int streamscall ldl_open(queue_t *, dev_t *, int, int, cred_t *);
STATIC int streamscall ldl_close(queue_t *, int, cred_t *);

STATIC int streamscall ldl_rsrv(queue_t *);
STATIC int streamscall ldl_wput(queue_t *, mblk_t *);
STATIC int streamscall ldl_wsrv(queue_t *);

STATIC struct qinit ldl_rinit = {
	.qi_srvp = ldl_rsrv,
	.qi_qopen = ldl_open,
	.qi_qclose = ldl_close,
	.qi_minfo = &ldl_minfo,
	.qi_mstat = &ldl_rdmstat,
};

STATIC struct qinit ldl_winit = {
	.qi_putp = ldl_wput,
	.qi_srvp = ldl_wsrv,
	.qi_minfo = &ldl_minfo,
	.qi_mstat = &ldl_wrmstat,
};

struct streamtab ldl_info = {
	.st_rdinit = &ldl_rinit,
	.st_wrinit = &ldl_winit,
};

#if defined(KERNEL_2_3)

#define ldldev		net_device
#define driver_started(dev)	 1
#define	START_BH_ATOMIC(dev)	spin_lock_bh(&(dev)->queue_lock)
#define	END_BH_ATOMIC(dev)	spin_unlock_bh(&(dev)->queue_lock)

#else

#define ldldev		device
#define netif_queue_stopped(dev) ((dev)->tbusy)
#define driver_started(dev)	 ((dev)->start)
#define	START_BH_ATOMIC(dev)	start_bh_atomic()
#define	END_BH_ATOMIC(dev)	end_bh_atomic()

#endif

#if defined(ARPHRD_IEEE802_TR)
#define	IS_ARPHRD_IEEE802_TR(dev) (dev)->type == ARPHRD_IEEE802_TR
#else
#define	IS_ARPHRD_IEEE802_TR(dev) 0
#endif

#define LDL_MAX_HDR_LEN	64

#define MAXADDRLEN	8
#define MAXSAPLEN	8
#define MAXDLSAPLEN	(MAXSAPLEN + MAXADDRLEN)

#define MINDATA		46
#define MAXDATA		4096

#define DONE		0
#define RETRY		1

typedef struct {
	unsigned char sap[MAXSAPLEN];
} sap_t;

#define LDLFLAG_PROMISC_SAP     0x0400
#define LDLFLAG_SET_ADDR	0x1000
#define LDLFLAG_HANGUP		0x2000
#define LDLFLAG_HANGUP_DONE	0x4000
#define LDLFLAG_PRIVATE		0x7400

#ifdef TC_PRIO_BULK
#define LDLPRI_LO	TC_PRIO_BULK
#define LDLPRI_MED	TC_PRIO_BESTEFFORT
#define LDLPRI_HI	TC_PRIO_INTERACTIVE
#else
#ifdef SOPRI_BACKGROUND
#define LDLPRI_LO	SOPRI_BACKGROUND
#define LDLPRI_MED	SOPRI_NORMAL
#define LDLPRI_HI	SOPRI_INTERACTIVE
#else
#define LDLPRI_LO       0
#define LDLPRI_MED      0
#define LDLPRI_HI       0
#endif
#endif

#define CONGESTION_BACKOFF_TICKS ((HZ/200 <= 1) ? 2 : (HZ/200))

struct ndev {
	dl_ulong magic;
	struct ldldev *dev;
	struct dl *endpoints;
	struct ndev *next;
	atomic_t wr_cur;
	int wr_max;
	int wr_min;
	toid_t tx_congest_timer;
	int sleeping;
};

struct pt {
	struct packet_type pt;
	dl_ulong magic;
	rwlock_t lock;
	struct sap *listen;
	struct pt *next;
};

struct sap {
	sap_t sap;
	dl_ulong magic;
	struct dl *dl;
	struct pt *pt;
	struct sap *next_listen;
	struct sap *next_sap;
};

struct dl {
	dl_ulong magic;
	queue_t *rq;
	ulong dlstate;
	dl_ulong flags;
	dl_ulong lost_rcv, lost_send;
	dl_ulong priority;
	struct dl *next_open;
	struct ndev *ndev;
	struct dl *next_ndev;
	int bufwait;
	int addr_len;
	int sap_len;
	int machdr_len;
	int machdr_reserve;
	int mtu;
	struct sap *sap;
	struct sap *subs;
	dl_ulong framing;
	unsigned char oui[3];

	int (*wantsframe) (struct dl * dl, unsigned char *fr, int len);
	mblk_t *(*rcvind) (struct dl * dl, mblk_t *dp);
	int (*mkhdr) (struct dl * dl, unsigned char *dst, int datalen, struct sk_buff * skb);
};

#define NDEV_MAGIC	0xA5B4C3D1
#define PT_MAGIC	0xA5B4C3D2
#define SAP_MAGIC	0xA5B4C3D3
#define DL_MAGIC	0xA5B4C3D4

#define DEV_WR_MIN 0x08000
#define DEV_WR_MAX 0x10000

typedef struct ldl_gstats {
	atomic_t attach_req_cnt;
	atomic_t detach_req_cnt;
	atomic_t bind_req_cnt;
	atomic_t unbind_req_cnt;
	atomic_t subs_bind_req_cnt;
	atomic_t subs_unbind_req_cnt;
	atomic_t udqos_req_cnt;
	atomic_t ok_ack_cnt;
	atomic_t error_ack_cnt;
	atomic_t unitdata_req_cnt;
	atomic_t unitdata_req_q_cnt;
	atomic_t unitdata_ind_cnt;
	atomic_t unitdata_q_cnt;
	atomic_t unitdata_drp_cnt;
	atomic_t uderror_ind_cnt;
	atomic_t ioctl_cnt;
	atomic_t net_rx_cnt;
	atomic_t net_rx_drp_cnt;
	atomic_t net_tx_cnt;
	atomic_t net_tx_fail_cnt;

} ldl_gstats_t;

typedef struct ldl_lstats {
	int to_be_done;
} ldl_lstats_t;

ldl_gstats_t ldl_gstats;
ldl_lstats_t ldl_lstats;

#define	ginc(field)	atomic_inc(&ldl_gstats.field)
STATIC unsigned long ldl_debug_mask;

#undef SPLSTR
#undef SPLX
STATIC spinlock_t ldl_spin_lock = SPIN_LOCK_UNLOCKED;

#define SPLSTR(__psw) while(0){ (void)(__psw); spin_lock(&ldl_spin_lock);   }
#define SPLX(__psw)   while(0){ (void)(__psw); spin_unlock(&ldl_spin_lock); }

STATIC struct pt *first_pt = NULL;
STATIC spinlock_t first_pt_lock = SPIN_LOCK_UNLOCKED;
STATIC struct ndev *first_ndev = NULL;
STATIC struct dl dl_dl[LDL_N_MINOR] = { {}, };

STATIC struct dl *first_open = NULL;
STATIC spinlock_t first_open_lock = SPIN_LOCK_UNLOCKED;

STATIC int n_hangup = 0;
STATIC int pt_n_alloc = 0;
STATIC int sap_n_alloc = 0;
STATIC int ndev_n_alloc = 0;

STATIC char *ldl_pkt_type(unsigned saptype);

#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_FUNC_4_ARGS
STATIC int rcv_func(struct sk_buff *skb, struct ldldev *dev, struct packet_type *pt,
		    struct ldldev *dev2);
#else
STATIC int rcv_func(struct sk_buff *skb, struct ldldev *dev, struct packet_type *pt);
#endif
STATIC int tx_func_raw(struct dl *dl, mblk_t *mp);

STATIC void
ldl_bfr_dump(char *msg, void *ptr, int len, int alldata)
{
	unsigned char *p = ptr;
	int bytes = 0;
	int trunc = 0;
	int nl = 0;

	printk("%s -- %d bytes\n", msg, len);

	if (!alldata && len > 24) {
		len = 24;
		trunc = 1;
	}

	for (; len > 0; len--, p++) {
		printk("%02x ", *p);
		nl = 0;
		if (!trunc && ++bytes == 16) {
			printk("\n");
			nl = 1;
			bytes = 0;
		}
	}

	if (!nl)
		printk("\n");
}

STATIC void
ldl_mp_dump(char *msg, mblk_t *mp, int alldata)
{
	ldl_bfr_dump(msg, mp->b_rptr, mp->b_wptr - mp->b_rptr, alldata);
}

STATIC void
ldl_mp_data_dump(char *msg, mblk_t *mp, int alldata)
{
	for (; mp != NULL && mp->b_datap->db_type != M_DATA; mp = mp->b_cont) ;
	if (mp == NULL)
		return;

	ldl_mp_dump(msg, mp, alldata);
}

STATIC void
ldl_skbuff_dump(char *msg, struct sk_buff *skb, int alldata)
{
#define	L	unsigned long
	int cnt;

	if (msg != NULL)
		printk("%s:\n", msg);

	printk("head=%lx data=%lx tail=%lx end=%lx truesize=%d\n"
	       "h.raw=%lx nh.raw=%lx mac.raw=%lx type=%s len=%d\n", (L) skb->head, (L) skb->data,
	       (L) skb->tail, (L) skb->end, skb->truesize, (L) skb->h.raw, (L) skb->nh.raw,
	       (L) skb->mac.raw, ldl_pkt_type(skb->pkt_type), skb->len);
	if (alldata)
		cnt = skb->tail - skb->head;
	else
		alldata = cnt = 64;

	ldl_bfr_dump("Dump from skb->head", skb->head, cnt, alldata);

#undef L
}

STATIC char *
ldl_framing_type(unsigned long framing)
{
	switch (framing & LDL_FRAME_MASK) {
	case LDL_FRAME_EII:
		return ("LDL_FRAME_EII");
	case LDL_FRAME_802_2:
		return ("LDL_FRAME_802_2");
	case LDL_FRAME_SNAP:
		return ("LDL_FRAME_SNAP");
	case LDL_FRAME_802_3:
		return ("LDL_FRAME_802_3");
	case LDL_FRAME_RAW_LLC:
		return ("LDL_FRAME_RAW_LLC");
	}
	return ("LDL_FRAME_UNKNOWN");
}

STATIC char *
ldl_pkt_type(unsigned saptype)
{
	switch (saptype) {
#ifdef ETH_P_ECHO
	case ETH_P_ECHO:
		return ("ETH_P_ECHO");
#endif
	case ETH_P_PUP:
		return ("ETH_P_PUP");
	case ETH_P_IP:
		return ("ETH_P_IP");
	case ETH_P_X25:
		return ("ETH_P_X25");
	case ETH_P_ARP:
		return ("ETH_P_ARP");
	case ETH_P_BPQ:
		return ("ETH_P_BPQ");
	case ETH_P_DEC:
		return ("ETH_P_DEC");
	case ETH_P_DNA_DL:
		return ("ETH_P_DNA_DL");
	case ETH_P_DNA_RC:
		return ("ETH_P_DNA_RC");
	case ETH_P_DNA_RT:
		return ("ETH_P_DNA_RT");
	case ETH_P_LAT:
		return ("ETH_P_LAT");
	case ETH_P_DIAG:
		return ("ETH_P_DIAG");
	case ETH_P_CUST:
		return ("ETH_P_CUST");
	case ETH_P_SCA:
		return ("ETH_P_SCA");
	case ETH_P_RARP:
		return ("ETH_P_RARP");
	case ETH_P_ATALK:
		return ("ETH_P_ATALK");
	case ETH_P_AARP:
		return ("ETH_P_AARP");
	case ETH_P_IPX:
		return ("ETH_P_IPX");
	case ETH_P_IPV6:
		return ("ETH_P_IPV6");
	case ETH_P_802_3:
		return ("ETH_P_802_3");
	case ETH_P_AX25:
		return ("ETH_P_AX25");
	case ETH_P_ALL:
		return ("ETH_P_ALL");
	case ETH_P_802_2:
		return ("ETH_P_802_2");
	case ETH_P_SNAP:
		return ("ETH_P_SNAP");
	case ETH_P_DDCMP:
		return ("ETH_P_DDCMP");
	case ETH_P_WAN_PPP:
		return ("ETH_P_WAN_PPP");
	case ETH_P_PPP_MP:
		return ("ETH_P_PPP_MP");
	case ETH_P_LOCALTALK:
		return ("ETH_P_LOCALTALK");
	case ETH_P_PPPTALK:
		return ("ETH_P_PPPTALK");
	case ETH_P_TR_802_2:
		return ("ETH_P_TR_802_2");
	case ETH_P_MOBITEX:
		return ("ETH_P_MOBITEX");
	case ETH_P_CONTROL:
		return ("ETH_P_CONTROL");
	case ETH_P_IRDA:
		return ("ETH_P_IRDA");
	case 0:
		return ("NULL(0)");
	}

	return ("ETH_P_UNKNOWN");
}

STATIC int
sap_create(struct dl *dl, sap_t dlsap, dl_ushort saptype)
{
	struct pt *pt, *npt;
	struct sap *sap;

	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(dl->ndev != NULL);

	saptype = htons(saptype);

	if ((sap = ALLOC(sizeof(*sap))) == NULL)
		return -1;
	++sap_n_alloc;
	memset(sap, 0, sizeof *sap);

	sap->magic = SAP_MAGIC;
	sap->dl = dl;
	sap->sap = dlsap;
	if (dl->sap == NULL) {

		sap->next_sap = NULL;
		dl->sap = sap;
	} else {

		sap->next_sap = dl->subs;
		dl->subs = sap;
	}

	npt = NULL;
	spin_lock(&first_pt_lock);
	do {
		for (pt = first_pt; pt; pt = pt->next)
			if (pt->pt.type == saptype && pt->pt.dev == dl->ndev->dev)
				break;
		if (pt == NULL) {
			if (npt == NULL) {
				spin_unlock(&first_pt_lock);
				if ((npt = ALLOC(sizeof(*pt))) == NULL) {
					FREE(sap);
					sap_n_alloc--;
					return -1;
				}
				++pt_n_alloc;
				memset(npt, 0, sizeof *npt);
				spin_lock(&first_pt_lock);
			} else {
				npt->next = first_pt;
				first_pt = pt = npt;
			}
		} else if (npt != NULL) {
			FREE(npt);
			npt = NULL;
			pt_n_alloc--;
		}
	} while (pt == NULL);
	sap->pt = pt;

	if (pt->listen == NULL) {

		ASSERT(pt->magic == 0);

		sap->next_listen = pt->listen;
		pt->listen = sap;
		rwlock_init(&pt->lock);
		pt->magic = PT_MAGIC;
		pt->pt.type = saptype;
		pt->pt.dev = dl->ndev->dev;
		pt->pt.func = rcv_func;
#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_AF_PACKET_PRIV
		pt->pt.af_packet_priv = (void *) 1;
#else
#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_DATA
		pt->pt.data = (void *) 1;
#else
#error Must have HAVE_KMEMB_STRUCT_PACKET_TYPE_DATA or HAVE_KMEMB_STRUCT_PACKET_TYPE_AF_PACKET_PRIV defined.
#endif
#endif
#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_NEXT
		pt->pt.next = NULL;
#else
#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_LIST
		pt->pt.list = (struct list_head) LIST_HEAD_INIT(pt->pt.list);
#else
#error Must have HAVE_KMEMB_STRUCT_PACKET_TYPE_NEXT or HAVE_KMEMB_STRUCT_PACKET_TYPE_LIST defined.
#endif
#endif
		spin_unlock(&first_pt_lock);
		dev_add_pack(&pt->pt);
	} else {

		ASSERT(pt->magic == PT_MAGIC);
		ASSERT(pt->pt.type == saptype);
		ASSERT(pt->pt.func == rcv_func);

		write_lock_bh(&pt->lock);
		sap->next_listen = pt->listen;
		pt->listen = sap;
		write_unlock_bh(&pt->lock);
		spin_unlock(&first_pt_lock);
	}

	return 0;
}

STATIC int
sap_destroy(struct dl *dl, struct sap *sap)
{
	pl_t psw;
	struct pt *pt, *opt;
	struct sap **sapp_dl, **sapp_pt;

	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(sap != NULL);
	ASSERT(sap->magic == SAP_MAGIC);
	ASSERT(dl->subs != NULL || sap == dl->sap);

	pt = sap->pt;
	ASSERT(pt != NULL);
	ASSERT(pt->magic == PT_MAGIC);

	SPLSTR(psw);
	spin_lock(&first_pt_lock);
	write_lock_bh(&pt->lock);
	if (pt->listen == sap && sap->next_listen == NULL) {

		write_unlock_bh(&pt->lock);
		if (pt == first_pt)
			first_pt = pt->next;
		else {
			for (opt = first_pt; opt->next != NULL; opt = opt->next)
				if (opt->next == pt) {
					opt->next = pt->next;
					break;
				}
		}
		spin_unlock(&first_pt_lock);
		dev_remove_pack(&pt->pt);
		pt->magic = 0;
		FREE(pt);
		--pt_n_alloc;
	} else {
		spin_unlock(&first_pt_lock);
		sapp_pt = &pt->listen;
		for (;;) {
			ASSERT(*sapp_pt != NULL);
			if (*sapp_pt == NULL) {

				write_unlock_bh(&pt->lock);
				SPLX(psw);
				return -1;
			}
			if (*sapp_pt == sap)
				break;
			sapp_pt = &(*sapp_pt)->next_listen;
		}
		*sapp_pt = sap->next_listen;
		write_unlock_bh(&pt->lock);
	}

	if (dl->sap == sap) {
		ASSERT(dl->subs == NULL);
		dl->sap = NULL;
	} else {
		sapp_dl = &dl->subs;
		for (;;) {
			ASSERT(*sapp_dl != NULL);
			if (*sapp_dl == NULL) {

				SPLX(psw);
				return -1;
			}
			ASSERT((*sapp_dl)->magic == SAP_MAGIC);
			ASSERT((*sapp_dl)->dl == dl);
			if (*sapp_dl == sap)
				break;
			sapp_dl = &(*sapp_dl)->next_sap;
		}
		*sapp_dl = sap->next_sap;
	}

	sap->dl = NULL;
	sap->pt = NULL;
	sap->magic = 0;
	FREE(sap);
	--sap_n_alloc;

	SPLX(psw);
	return 0;
}

STATIC void
sap_destroy_all(struct dl *dl)
{
	int ret;

	ASSERT(dl != NULL);

	while (dl->subs) {
		ASSERT(dl->subs->pt != NULL);
		ret = sap_destroy(dl, dl->subs);
		ASSERT(ret == 0);
		if (ret != 0)
			return;
	}
	if (dl->sap) {
		ASSERT(dl->sap->pt != NULL);
		ret = sap_destroy(dl, dl->sap);
		ASSERT(ret == 0);
	}
}

STATIC INLINE void
hangup_set(struct dl *dl)
{
	if ((dl->flags & LDLFLAG_HANGUP) == 0) {

		ASSERT(dl->dlstate != DL_UNATTACHED);

		ASSERT((dl->flags & LDLFLAG_HANGUP_DONE) != 0);

		ASSERT(dl->ndev == NULL);

		dl->flags |= LDLFLAG_HANGUP;
		++n_hangup;
	}
}

STATIC void ndev_release(struct dl *dl);
STATIC INLINE void
hangup_do(struct dl *dl)
{
	pl_t psw;

	SPLSTR(psw);

	ASSERT((dl->flags & LDLFLAG_HANGUP) != 0);

	if ((dl->flags & LDLFLAG_HANGUP_DONE) != 0) {
		SPLX(psw);
		return;
	}

	ASSERT(n_hangup > 0);

	if (dl->dlstate == DL_UNBIND_PENDING || dl->dlstate == DL_SUBS_UNBIND_PND) {
		SPLX(psw);
		return;
	}

	if (dl->dlstate == DL_IDLE) {
		sap_destroy_all(dl);
		dl->dlstate = DL_UNBOUND;
	}
	if (dl->dlstate == DL_UNBOUND) {

		ndev_release(dl);
		dl->addr_len = 0;
		dl->ndev = NULL;
		dl->dlstate = DL_UNATTACHED;
	}
	ASSERT(dl->dlstate == DL_UNATTACHED);

	SPLX(psw);

	if (putctl(dl->rq->q_next, M_HANGUP)) {
		SPLSTR(psw);
		if ((dl->flags & LDLFLAG_HANGUP_DONE) == 0) {
			dl->flags |= LDLFLAG_HANGUP_DONE;
			--n_hangup;
		}
		SPLX(psw);
	} else
		printk("ldl: cannot send M_HANGUP\n");
}

STATIC INLINE struct ndev *
ndev_find(struct ldldev *dev)
{
	struct ndev *ndev;

	for (ndev = first_ndev; ndev; ndev = ndev->next)
		if (ndev->dev == dev)
			break;
	return ndev;
}

STATIC struct ndev *
ndev_get(dl_ulong ppa)
{
	dl_ulong i;
	struct ldldev *dev;
	struct ndev *ndev;

	for (dev = dev_base, i = 0; dev; dev = dev->next, ++i)
		if (ppa == i)
			break;

	if (dev == NULL)
		return NULL;
	ASSERT(ppa == i);

	if ((ndev = ndev_find(dev)) == NULL) {
		if ((ndev = ALLOC(sizeof(*ndev))) == NULL)
			return NULL;
		++ndev_n_alloc;
		memset(ndev, 0, sizeof *ndev);
		ndev->magic = NDEV_MAGIC;
		ndev->dev = dev;
		ndev->wr_max = DEV_WR_MAX;
		ndev->wr_min = DEV_WR_MIN;
	}

	return ndev;
}

STATIC void
ndev_attach(struct ndev *ndev, struct dl *dl)
{
	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);

	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(dl->ndev == NULL);

	dl->ndev = ndev;
	dl->next_ndev = ndev->endpoints;
	ndev->endpoints = dl;
}

STATIC void
ndev_free(struct ndev *ndev)
{
	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);
	ASSERT(ndev->dev == NULL);

	if (atomic_read(&ndev->wr_cur) > 0 || ndev->endpoints != NULL)
		return;

	if (ndev->tx_congest_timer) {

		untimeout(ndev->tx_congest_timer);
		ndev->tx_congest_timer = 0;
	}
	ndev->magic = 0;
	FREE(ndev);
	--ndev_n_alloc;
}

STATIC void
ndev_release(struct dl *dl)
{
	struct ndev *ndev = dl->ndev;
	struct dl **dlp;

	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);
	for (dlp = &ndev->endpoints; *dlp; dlp = &(*dlp)->next_ndev) {
		ASSERT((*dlp)->magic == DL_MAGIC);
		ASSERT((*dlp)->ndev == ndev);
		if (*dlp == dl)
			break;
	}
	ASSERT(*dlp == dl);
	*dlp = dl->next_ndev;
	dl->ndev = NULL;
	if (ndev->endpoints == NULL) {

		if (ndev->dev != NULL) {

			START_BH_ATOMIC(ndev->dev);
			qdisc_reset(ndev->dev->qdisc);
			END_BH_ATOMIC(ndev->dev);
			ndev->dev = NULL;
		}
		ndev_free(ndev);
	}
}

STATIC void
ndev_down(struct ndev *ndev, int hard)
{
	struct dl *dl;

	ASSERT(ndev->magic == NDEV_MAGIC);

	if (hard)
		ndev->dev = NULL;
	for (dl = ndev->endpoints; dl; dl = dl->next_ndev) {
		ASSERT(dl->magic == DL_MAGIC);

		if (hard || (dl->flags & LDLFLAG_SET_ADDR) == 0) {
			ndev_release(dl);
			hangup_set(dl);
		}
	}
}

STATIC void
ndev_wr_wakeup_endp(struct ndev *ndev)
{
	struct dl *dl;

	ASSERT(ndev->magic == NDEV_MAGIC);

	for (dl = ndev->endpoints; dl; dl = dl->next_ndev) {
		ASSERT(dl->magic == DL_MAGIC);
		ASSERT(dl->ndev == ndev);

		qenable(WR(dl->rq));
	}
}

STATIC void
ndev_wr_wakeup(struct ndev *ndev)
{
	ASSERT(ndev->magic == NDEV_MAGIC);
	ASSERT(ndev->sleeping);

	if (ndev->tx_congest_timer) {
		untimeout(ndev->tx_congest_timer);
		ndev->tx_congest_timer = 0;
	}
	ndev->sleeping = 0;

	ndev_wr_wakeup_endp(ndev);
}

STATIC void streamscall
tx_congestion_timeout(caddr_t dp)
{
	pl_t psw;
	struct ndev *ndev = (struct ndev *) dp;

	SPLSTR(psw);

	ASSERT(ndev->magic == NDEV_MAGIC);
	if (ndev->tx_congest_timer != 0) {
		ndev->tx_congest_timer = 0;
		ndev_wr_wakeup_endp(ndev);
	}

	SPLX(psw);
}

STATIC void
ndev_wr_sleep(struct ndev *ndev)
{
	ASSERT(!ndev->sleeping);

	if (!ndev->tx_congest_timer)
		ndev->tx_congest_timer =
		    timeout(tx_congestion_timeout, (caddr_t) ndev, CONGESTION_BACKOFF_TICKS);
	if (ndev->tx_congest_timer)
		ndev->sleeping = 1;
	else
		printk("ldl: ndev_wr_sleep() failed\n");
}

STATIC void
ndev_skb_destruct(struct sk_buff *skb)
{
	struct ndev *ndev;

	ASSERT(skb != NULL);

#ifdef KERNEL_2_1
	if (skb_cloned(skb))
		return;
#else
	if (skb->data_skb != skb)
		return;
#endif

	ndev = (struct ndev *) skb->sk;
	skb->sk = NULL;
	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);

	ASSERT(atomic_read(&ndev->wr_cur) >= skb->truesize);
	atomic_sub(skb->truesize, &ndev->wr_cur);

	if (ndev->dev != NULL) {
		if (atomic_read(&ndev->wr_cur) <= ndev->wr_min) {
			if (ndev->sleeping)
				ndev_wr_wakeup(ndev);
		}
	} else {
		ASSERT(ndev->endpoints == NULL);

		if (atomic_read(&ndev->wr_cur) == 0)
			ndev_free(ndev);
	}
}

#ifdef KERNEL_2_1

STATIC int
ndev_xmit(struct ndev *ndev, struct sk_buff *skb)
{
	pl_t psw;
	int ret;

	ASSERT(skb != NULL);
	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);
	ASSERT(ndev->dev != NULL);

	skb->mac.raw = skb->data;
	skb->dev = ndev->dev;
	atomic_add(skb->truesize, &ndev->wr_cur);
	skb->sk = (struct sock *) ndev;
	skb->destructor = ndev_skb_destruct;

	if (atomic_read(&ndev->wr_cur) <= ndev->wr_max) {
		switch (dev_queue_xmit(skb)) {
		case NET_XMIT_SUCCESS:
		default:
			return DONE;

		case NET_XMIT_DROP:
			return RETRY;

		case NET_XMIT_CN:
		case NET_XMIT_POLICED:
			ret = DONE;
			break;
		}
	} else {
		kfree_skb(skb);
		ret = RETRY;
	}

	SPLSTR(psw);
	if (!ndev->sleeping)
		ndev_wr_sleep(ndev);
	SPLX(psw);
	return ret;
}

#else
#ifdef too_complicated_KERNEL_2_1

STATIC int
ndev_xmit(struct ndev *ndev, struct sk_buff *skb)
{
	pl_t psw;
	struct ldldev *dev;

	ASSERT(skb != NULL);
	ASSERT(ndev != NULL);
	ASSERT(ndev->magic == NDEV_MAGIC);
	ASSERT(ndev->dev != NULL);

	skb->mac.raw = skb->data;
	skb->dev = dev = ndev->dev;
	atomic_add(skb->truesize, &ndev->wr_cur);
	(struct ndev *) skb->sk = ndev;
	skb->destructor = ndev_skb_destruct;

	if (atomic_read(&ndev->wr_cur) <= ndev->wr_max) {
		struct Qdisc *q;

		START_BH_ATOMIC(dev);
		q = dev->qdisc;
		ASSERT(q != NULL);
		if (q->enqueue) {
			int ret;

			ret = q->enqueue(skb, q);
			qdisc_wakeup(dev);
			END_BH_ATOMIC(dev);
			if (ret == 1)
				return DONE;
		} else {
			END_BH_ATOMIC(dev);
			if (dev_queue_xmit(skb) >= 0)
				return DONE;
		}
		SPLSTR(psw);
		if (!ndev->sleeping)
			ndev_wr_sleep(ndev);
		SPLX(psw);
		return RETRY;
	} else {
		SPLSTR(psw);
		if (!ndev->sleeping)
			ndev_wr_sleep(ndev);
		SPLX(psw);
		kfree_skb(skb);
		return RETRY;
	}
}

#else

STATIC int
ndev_xmit(struct ndev *ndev, struct sk_buff *skb, int pri)
{
	atomic_add(skb->truesize, &ndev->wr_cur);
	(struct ndev *) skb->sk = ndev;
	skb->dev = ndev->dev;
	skb->arp = 1;
	skb->destructor = ndev_skb_destruct;
	dev_queue_xmit(skb, ndev->dev, pri);
	return DONE;
}
#endif
#endif

STATIC int
device_notification(struct notifier_block *notifier, unsigned long event, void *ptr)
{
	struct ldldev *dev = ptr;
	struct ndev *d;
	struct dl *dl;
	pl_t psw;

	SPLSTR(psw);

#ifdef KERNEL_2_1
	if (event == NETDEV_DOWN || event == NETDEV_UNREGISTER) {
#else
	if (event == NETDEV_DOWN) {
#endif
		if ((d = ndev_find(dev)) != NULL) {
			ndev_down(d, event != NETDEV_DOWN);
			if (n_hangup) {
				SPLX(psw);
				spin_lock(&first_open_lock);
				for (dl = first_open; dl; dl = dl->next_open)
					if ((dl->flags & LDLFLAG_HANGUP) != 0)
						hangup_do(dl);
				spin_unlock(&first_open_lock);
				return NOTIFY_DONE;
			}
		}
	}

	SPLX(psw);

	return NOTIFY_DONE;
}

STATIC struct notifier_block dl_notifier = {
	device_notification,
	NULL,
	0
};

STATIC INLINE int
notifier_register(void)
{
	return register_netdevice_notifier(&dl_notifier);
}

STATIC INLINE int
notifier_unregister(void)
{
	return unregister_netdevice_notifier(&dl_notifier);
}

STATIC void streamscall
dl_bufcallback(long idx)
{
	struct dl *dl = &dl_dl[idx];

	ASSERT(dl->rq != NULL);
	ASSERT(dl->bufwait);

	dl->bufwait = 0;
	qenable(WR(dl->rq));
}

STATIC int
dl_bufcall(struct dl *dl, mblk_t *mp, int size)
{
	ASSERT(!dl->bufwait);
	if ((dl->bufwait = bufcall(size, BPRI_HI, dl_bufcallback, dl - dl_dl)) == 0) {
		printk("ldl: bufcall failed\n");
		freemsg(mp);
		return DONE;
	}
	return RETRY;
}

STATIC INLINE int
pri_dlpi2netdevice(dl_ulong pri)
{
	ASSERT(pri >= 0);
	ASSERT(pri <= 100);

	return (pri < 33) ? LDLPRI_HI : (pri < 66) ? LDLPRI_MED : LDLPRI_LO;
}

STATIC INLINE int
reuse_msg(mblk_t *mp, dl_ushort size)
{
	mblk_t *bp;

	ASSERT(mp != NULL);
	ASSERT(mp->b_datap != NULL);

	if (mp->b_datap->db_lim - mp->b_datap->db_base < size) ;
	return 0;
	if (mp->b_datap->db_ref != 1)
		return 0;

	if ((bp = unlinkb(mp)) != NULL)
		freemsg(bp);
	mp->b_wptr = mp->b_rptr = mp->b_datap->db_base;

	return 1;
}

STATIC INLINE void
make_dl_ok_ack(mblk_t *mp, dl_ulong primitive)
{
	dl_ok_ack_t *ackp;

	mp->b_datap->db_type = M_PCPROTO;
	ackp = (dl_ok_ack_t *) mp->b_datap->db_base;
	ackp->dl_primitive = DL_OK_ACK;
	ackp->dl_correct_primitive = primitive;
	mp->b_wptr += DL_OK_ACK_SIZE;
}

STATIC INLINE void
make_dl_error_ack(mblk_t *mp, dl_ulong primitive, dl_ulong err, dl_ulong uerr)
{
	dl_error_ack_t *ackp;

	mp->b_datap->db_type = M_PCPROTO;
	ackp = (dl_error_ack_t *) mp->b_datap->db_base;
	ackp->dl_primitive = DL_ERROR_ACK;
	ackp->dl_error_primitive = primitive;
	ackp->dl_errno = err;
	ackp->dl_unix_errno = uerr;
	mp->b_wptr += DL_ERROR_ACK_SIZE;
}

STATIC INLINE int
do_ok_ack(struct dl *dl, mblk_t **mp, dl_ulong primitive)
{
	if (!reuse_msg(*mp, DL_OK_ACK_SIZE)) {
		mblk_t *bp;

		if ((bp = allocb(DL_OK_ACK_SIZE, BPRI_HI)) == NULL) {
			if (dl_bufcall(dl, *mp, DL_OK_ACK_SIZE) == RETRY)
				return RETRY;
			else {
				*mp = NULL;
				return DONE;
			}
		}
		freemsg(*mp);
		*mp = bp;
	}
	make_dl_ok_ack(*mp, primitive);
	return DONE;
}

STATIC int
reply_error_ack(struct dl *dl, mblk_t *mp, dl_ulong primitive, dl_ulong err, dl_ulong uerr)
{
	if (!reuse_msg(mp, DL_ERROR_ACK_SIZE)) {
		mblk_t *bp;

		if ((bp = allocb(DL_ERROR_ACK_SIZE, BPRI_HI)) == NULL)
			return dl_bufcall(dl, mp, DL_ERROR_ACK_SIZE);
		freemsg(mp);
		mp = bp;
	}
	make_dl_error_ack(mp, primitive, err, uerr);
	putnext(dl->rq, mp);
	ginc(error_ack_cnt);
	return DONE;
}

STATIC int
eth_ii_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(dl->sap_len == 2);
	if (len < 14)
		return 0;
	if (*(short *) (fr + 12) != *(short *) dl->sap->sap.sap) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (*(short *) (fr + 12) == *(short *) sap->sap.sap)
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}
	if (memcmp(fr, dl->ndev->dev->dev_addr, 6) && memcmp(fr, dl->ndev->dev->broadcast, 6))
		return 0;
	return 1;
}

STATIC mblk_t *
eth_ii_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	unsigned short dsap = ntohs(*(unsigned short *) (dp->b_rptr + 12));

	ASSERT(dl->sap_len == 2);
	ASSERT(dl->addr_len == 6);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 16, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 8;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE + 8;
	ud->dl_src_addr_length = 8;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_group_address = *dp->b_rptr & 1;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, dp->b_rptr + 6, 6);
	bp->b_wptr += 6;
	*(unsigned short *) (bp->b_wptr) = dsap;
	bp->b_wptr += 2;

	memcpy(bp->b_wptr, dp->b_rptr, 6);
	bp->b_wptr += 6;
	*(unsigned short *) (bp->b_wptr) = dsap;
	bp->b_wptr += 2;

	dp->b_rptr += 14;

	linkb(bp, dp);
	return bp;
}

STATIC int
eth_ii_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	unsigned char *hdr;
	unsigned short dsap;

	dsap = htons(*(unsigned short *) (dst + 6));
	hdr = skb_push(skb, 14);
	memcpy(hdr, dst, 6);
	memcpy(hdr + 6, dl->ndev->dev->dev_addr, 6);
	*(unsigned short *) (hdr + 12) = dsap;

	if (dsap != *(unsigned short *) dl->sap->sap.sap) {
		struct sap *sap;

		for (sap = dl->subs; sap; sap = sap->next_sap)
			if (dsap == *(unsigned short *) sap->sap.sap)
				return 1;
		return 0;
	}
	return 1;
}

STATIC int
eth_8022_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(dl->sap_len == 1);

	if (len < 17 || (!(dl->flags & LDLFLAG_RAW) && *(fr + 16) != 0x03))
		return 0;
	if (ntohs(*(unsigned short *) (fr + 12)) < 3)
		return 0;
	if (dl->flags & LDLFLAG_PROMISC_SAP)
		return 1;
	if (*(fr + 14) != dl->sap->sap.sap[0]) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (*(fr + 14) == sap->sap.sap[0])
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}
#if 0
	{
		int i;

		printk("eth_8022_want: SAP checked, MAC@ not yet ...");
		for (i = 0; i < 16; i++)
			printk("%02x ", fr[i]);
		printk("\n");
	}
#endif

	if (!memcmp(fr, dl->ndev->dev->dev_addr, 6))
		return 1;
	if (!memcmp(fr, dl->ndev->dev->broadcast, 6))
		return 1;

	{
		struct dev_mc_list *dmi = dl->ndev->dev->mc_list;

		while (dmi != NULL)
			if (!memcmp(fr, dmi->dmi_addr, 6))
				return 1;
			else
				dmi = dmi->next;
	}
	return 0;
}

STATIC mblk_t *
eth_8022_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	unsigned short len;

	ASSERT(dl->sap_len == 1);
	ASSERT(dl->addr_len == 6);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 14, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 7;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE + 7;
	ud->dl_src_addr_length = 7;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_group_address = *dp->b_rptr & 1;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, dp->b_rptr + 6, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = *(dp->b_rptr + 15);

	memcpy(bp->b_wptr, dp->b_rptr, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = *(dp->b_rptr + 14);

	len = ntohs(*(unsigned short *) (dp->b_rptr + 12)) - 3;
	dp->b_rptr += 17;
	if (dp->b_wptr - dp->b_rptr > len)
		dp->b_wptr = dp->b_rptr + len;

	linkb(bp, dp);
	return bp;
}

STATIC int
eth_8022_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	unsigned char *hdr;
	unsigned char dsap;

	dsap = *(dst + 6);
	if (dsap != dl->sap->sap.sap[0]) {
		struct sap *sap;

		for (sap = dl->subs; sap; sap = sap->next_sap)
			if (dsap == sap->sap.sap[0])
				break;
		if (sap == NULL)
			return 0;
	}

	hdr = skb_push(skb, 17);
	memcpy(hdr, dst, 6);
	memcpy(hdr + 6, dl->ndev->dev->dev_addr, 6);
	*(unsigned short *) (hdr + 12) = htons(datalen + 3);
	*(hdr + 14) = *(hdr + 15) = dsap;
	*(hdr + 16) = 3;
	return 1;
}

STATIC int
eth_raw8022_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(dl->sap_len == 1);

	if (len < 17 || ntohs(*(unsigned short *) (fr + 12)) < 3)
		return 0;
	if (*(unsigned short *) (fr + 14) == 0xffff)
		return 0;
	if (dl->flags & LDLFLAG_PROMISC_SAP)
		return 1;
	if (*(fr + 14) != dl->sap->sap.sap[0]) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (*(fr + 14) == sap->sap.sap[0])
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}
	if (memcmp(fr, dl->ndev->dev->dev_addr, 6) && memcmp(fr, dl->ndev->dev->broadcast, 6))
		return 0;
	return 1;
}

STATIC mblk_t *
eth_raw8022_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	unsigned short len;

	ASSERT(dl->sap_len == 1);
	ASSERT(dl->addr_len == 6);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 14, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 7;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE + 7;
	ud->dl_src_addr_length = 7;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_group_address = *dp->b_rptr & 1;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, dp->b_rptr + 6, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = *(dp->b_rptr + 15);

	memcpy(bp->b_wptr, dp->b_rptr, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = *(dp->b_rptr + 14);

	len = ntohs(*(unsigned short *) (dp->b_rptr + 12));
	dp->b_rptr += 14;
	if (dp->b_wptr - dp->b_rptr > len)
		dp->b_wptr = dp->b_rptr + len;

	linkb(bp, dp);
	return bp;
}

STATIC int
eth_raw8022_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	unsigned char *hdr;

	hdr = skb_push(skb, 14);
	memcpy(hdr, dst, 6);
	memcpy(hdr + 6, dl->ndev->dev->dev_addr, 6);
	*(unsigned short *) (hdr + 12) = htons(datalen);
	*(hdr + 14) = *(dst + 6);
	*(hdr + 15) = dl->sap->sap.sap[0];
	return 1;
}

STATIC int
eth_snap_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(dl->sap_len == 2);
	if (len < 22 || ntohs(*(unsigned short *) (fr + 12)) < 8
	    || *(unsigned short *) (fr + 14) != 0xAAAA || *(fr + 16) != 0x03
	    || memcmp(dl->oui, fr + 17, 3))
		return 0;

	if (memcmp(fr + 20, dl->sap->sap.sap, 2)) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (!memcmp(fr + 20, sap->sap.sap, 2))
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}

	if (memcmp(fr, dl->ndev->dev->dev_addr, 6) && memcmp(fr, dl->ndev->dev->broadcast, 6))
		return 0;

	return 1;
}

STATIC mblk_t *
eth_snap_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	unsigned short len;
	unsigned short dsap = ntohs(*(unsigned short *) (dp->b_rptr + 17));

	ASSERT(dl->sap_len == 2);
	ASSERT(dl->addr_len == 6);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 22, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 8;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE + 8;
	ud->dl_src_addr_length = 8;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_group_address = *dp->b_rptr & 1;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, dp->b_rptr + 6, 6);
	bp->b_wptr += 6;
	*(unsigned short *) (bp->b_wptr) = dsap;
	bp->b_wptr += 2;

	memcpy(bp->b_wptr, dp->b_rptr, 6);
	bp->b_wptr += 6;
	*(unsigned short *) (bp->b_wptr) = dsap;
	bp->b_wptr += 2;

	len = ntohs(*(unsigned short *) (dp->b_rptr + 12)) - 8;
	dp->b_rptr += 22;
	if (dp->b_wptr - dp->b_rptr > len)
		dp->b_wptr = dp->b_rptr + len;

	linkb(bp, dp);
	return bp;
}

STATIC int
eth_snap_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	unsigned char *hdr;
	unsigned short dsap;

	dsap = *(unsigned short *) (dst + 6);
	if (dsap != *(unsigned short *) dl->sap->sap.sap) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (dsap == *(unsigned short *) sap->sap.sap)
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}

	hdr = skb_push(skb, 22);
	memcpy(hdr, dst, 6);
	memcpy(hdr + 6, dl->ndev->dev->dev_addr, 6);
	*(unsigned short *) (hdr + 12) = htons(datalen + 8);
	*(unsigned short *) (hdr + 14) = 0xaaaa;
	*(hdr + 16) = 3;
	memcpy(hdr + 17, dl->oui, 3);
	*(unsigned short *) (hdr + 20) = dsap;
	return 1;
}

STATIC int
ipx_8023_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(dl->sap_len == 0);

	if (len < 44 || ntohs(*(unsigned short *) (fr + 12)) < 30)
		return 0;

	if (ntohs(*(unsigned short *) (fr + 14)) != 0xffff)
		return 0;
	if (memcmp(fr, dl->ndev->dev->dev_addr, 6) && memcmp(fr, dl->ndev->dev->broadcast, 6))
		return 0;
	return 1;
}

STATIC mblk_t *
ipx_8023_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	unsigned short len;

	ASSERT(dl->sap_len == 0);
	ASSERT(dl->addr_len == 6);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 12, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 6;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_src_addr_length = 6;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE + 6;
	ud->dl_group_address = *dp->b_rptr & 1;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, dp->b_rptr, 12);
	bp->b_wptr += 12;

	len = *(unsigned short *) (dp->b_rptr + 12);
	dp->b_rptr += 14;
	if (dp->b_wptr - dp->b_rptr > len)
		dp->b_wptr = dp->b_rptr + len;

	linkb(bp, dp);
	return bp;
}

STATIC int
ipx_8023_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	unsigned char *hdr;

	hdr = skb_push(skb, 14);
	memcpy(hdr, dst, 6);
	memcpy(hdr + 6, dl->ndev->dev->dev_addr, 6);
	*(unsigned short *) (hdr + 12) = htons(datalen);
	return 1;
}

typedef struct tr_hdr {
	u8 acf;
	u8 fcf;
	u8 dst_addr[6];
	u8 src_addr[6];

	u8 bl;
	u8 df;

} tr_hdr_t;

typedef struct tr_llc_frm_hdr {
	u8 llc_dsap;
	u8 llc_ssap;
	u8 llc_ctl[1];

} tr_llc_frm_hdr_t;

#define SADDR_0_RTE_PRES        0x80
#define RCF_0_LLLLL             0x1F
#define FCF_FF          	0xC0
#define     FCF_MAC     	0x00
#define     FCF_LLC     	0x40

STATIC int
tr_8022_want(struct dl *dl, unsigned char *fr, int len)
{
	tr_hdr_t *trp;
	tr_llc_frm_hdr_t *llcp;
	int rtelgth;

#if 0
	{
		int i;

		printk("tr_8022_want:\n");
		for (i = 0; i < 16; i++)
			printk("%02x ", fr[i]);
		printk("\n");
		for (; i < 32; i++)
			printk("%02x ", fr[i]);
		printk("\n");
	}
#endif

	trp = (tr_hdr_t *) fr;
	ASSERT(dl->sap_len == 1);
	if (len < sizeof(tr_hdr_t) - 2 + sizeof(tr_llc_frm_hdr_t))
		return 0;

	if ((trp->fcf & FCF_FF) != FCF_LLC)
		return (0);

	if (trp->src_addr[0] & SADDR_0_RTE_PRES) {
		rtelgth = trp->bl & RCF_0_LLLLL;
		if (rtelgth < 2)
			return 0;

		if (rtelgth & 0x01)
			return 0;

		llcp = (tr_llc_frm_hdr_t *) (fr + sizeof(*trp) - 2 + rtelgth);
	} else {

		llcp = (tr_llc_frm_hdr_t *) (fr + sizeof(*trp) - 2);
	}

	if (!(dl->flags & LDLFLAG_RAW) && llcp->llc_ctl[0] != 0x03)
		return 0;

	if (dl->flags & LDLFLAG_PROMISC_SAP)
		return 1;

	if (llcp->llc_dsap != dl->sap->sap.sap[0]) {
		struct sap *sap = dl->subs;

		while (sap != NULL) {
			if (llcp->llc_dsap == sap->sap.sap[0])
				break;
			sap = sap->next_sap;
		}
		if (sap == NULL)
			return 0;
	}

	return 1;
}

STATIC mblk_t *
tr_8022_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;
	tr_hdr_t *trp;
	tr_llc_frm_hdr_t *llcp;
	int rtelgth = 0;

	ASSERT(dl->sap_len == 1);
	ASSERT(dl->addr_len == 6);

	trp = (tr_hdr_t *) dp->b_rptr;
	if (trp->src_addr[0] & SADDR_0_RTE_PRES) {
		rtelgth = trp->bl & RCF_0_LLLLL;
	}

	llcp = (tr_llc_frm_hdr_t *) (dp->b_rptr + sizeof(*trp) - 2 + rtelgth);

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 14, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 7;
	ud->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE + 7;
	ud->dl_src_addr_length = 7;
	ud->dl_src_addr_offset = DL_UNITDATA_IND_SIZE;
	ud->dl_group_address = 0;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	memcpy(bp->b_wptr, trp->dst_addr, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = llcp->llc_dsap;
	memcpy(bp->b_wptr + 6, trp->src_addr, 6);
	bp->b_wptr += 6;
	*bp->b_wptr++ = llcp->llc_ssap;

	dp->b_rptr += sizeof(*trp) - 2 + rtelgth + sizeof(tr_llc_frm_hdr_t);

	linkb(bp, dp);
	return bp;
}

STATIC int
tr_8022_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	tr_hdr_t *trp;
	tr_llc_frm_hdr_t *llcp;
	unsigned char dsap;

	dsap = *(dst + 6);
	if (dsap != dl->sap->sap.sap[0]) {
		struct sap *sap;

		for (sap = dl->subs; sap; sap = sap->next_sap)
			if (dsap == sap->sap.sap[0])
				break;
		if (sap == NULL)
			return 0;
	}

	trp = (tr_hdr_t *) skb_push(skb, sizeof(tr_hdr_t) - 2 + sizeof(*llcp));

	trp->acf = 0x10;
	trp->fcf = 0x40;
	memcpy(trp->dst_addr, dst, 6);
	memcpy(trp->src_addr, dl->ndev->dev->dev_addr, 6);
	trp->src_addr[0] &= ~SADDR_0_RTE_PRES;

	llcp = (tr_llc_frm_hdr_t *) (((char *) (trp + 1)) - 2);
	llcp->llc_dsap = dsap;
	llcp->llc_ssap = dsap;
	llcp->llc_ctl[0] = 0x03;

	return 1;
}

STATIC int
hdlc_raw_want(struct dl *dl, unsigned char *fr, int len)
{
	return 1;
}

STATIC mblk_t *
hdlc_raw_rcvind(struct dl *dl, mblk_t *dp)
{
	mblk_t *bp;
	dl_unitdata_ind_t *ud;

	if ((bp = allocb(DL_UNITDATA_IND_SIZE + 14, BPRI_LO)) == NULL) {
		freeb(dp);
		return NULL;
	}

	bp->b_datap->db_type = M_PROTO;
	ud = (dl_unitdata_ind_t *) bp->b_rptr;
	ud->dl_primitive = DL_UNITDATA_IND;
	ud->dl_dest_addr_length = 0;
	ud->dl_dest_addr_offset = 0;
	ud->dl_src_addr_length = 0;
	ud->dl_src_addr_offset = 0;
	ud->dl_group_address = 0;
	bp->b_wptr += DL_UNITDATA_IND_SIZE;

	linkb(bp, dp);
	return bp;
}

STATIC int
hdlc_raw_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	return 1;
}

STATIC int
fddi_8022_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(0);
	return 0;
}

STATIC mblk_t *
fddi_8022_rcvind(struct dl *dl, mblk_t *dp)
{
	ASSERT(0);
	return NULL;
}

STATIC int
fddi_8022_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	ASSERT(0);
	return 0;
}

STATIC int
fddi_snap_want(struct dl *dl, unsigned char *fr, int len)
{
	ASSERT(0);
	return 0;
}

STATIC mblk_t *
fddi_snap_rcvind(struct dl *dl, mblk_t *dp)
{
	ASSERT(0);
	return NULL;
}

STATIC int
fddi_snap_mkhdr(struct dl *dl, unsigned char *dst, int datalen, struct sk_buff *skb)
{
	ASSERT(0);
	return 0;
}

STATIC INLINE void
dl_rcv_put(mblk_t *dp, struct dl *dl, int copy)
{
	mblk_t *mp;

	if (dp == NULL) {
		if (copy == 0)
			freeb(dp);
		++dl->lost_rcv;
		return;
	}

	if (copy != 0 && (dp = dupb(dp)) == NULL) {
		++dl->lost_rcv;
		ginc(net_rx_drp_cnt);
		return;
	}

	ginc(net_rx_cnt);
	if ((dl->flags & LDLFLAG_RAW) == 0) {
		if ((mp = dl->rcvind(dl, dp)) == NULL) {
			freeb(dp);
			++dl->lost_rcv;
			return;
		}
	} else
		mp = dp;
#if 1

	if (canput(dl->rq)) {
		if (!putq(dl->rq, mp))
			freemsg(mp);
		ginc(unitdata_q_cnt);
	} else {
		freemsg(mp);
		ginc(unitdata_drp_cnt);
		++dl->lost_rcv;
	}
#else
	if (qsize(dl->rq) == 0 && canputnext(dl->rq)) {
		if (ldl_debug_mask & LDL_DEBUG_UDIND)
			ldl_mp_data_dump("ldl_unitdata_ind", mp,
					 ldl_debug_mask & LDL_DEBUG_ALLDATA);
		putnext(dl->rq, mp);
		ginc(unitdata_ind_cnt);
	} else if (canput(dl->rq)) {
		if (!putq(dl->rq, mp))
			freemsg(mp);
		ginc(unitdata_q_cnt);
	} else {
		freemsg(mp);
		ginc(unitdata_drp_cnt);
		++dl->lost_rcv;
	}
#endif
}

void streamscall
mblk_destructor(caddr_t arg)
{
	kfree_skb((struct sk_buff *) arg);
}

STATIC int
#ifdef HAVE_KMEMB_STRUCT_PACKET_TYPE_FUNC_4_ARGS
rcv_func(struct sk_buff *skb, struct ldldev *dev, struct packet_type *pt, struct ldldev *dev2)
#else
rcv_func(struct sk_buff *skb, struct ldldev *dev, struct packet_type *pt)
#endif
{
	mblk_t *dp;
	struct dl *dl, *last = NULL;
	struct sap *sap;

#if 0
	struct ethhdr *hdr = (struct ethhdr *) skb->mac.raw;
#endif
	unsigned char *fr_ptr, fr_buf[LDL_MAX_HDR_LEN];
	int fr_len;
	struct free_rtn mblk_rtn;

	ASSERT(dev->type == ARPHRD_ETHER || dev->type == ARPHRD_LOOPBACK
	       || dev->type == ARPHRD_IEEE802 || IS_ARPHRD_IEEE802_TR(dev)
	       || dev->type == ARPHRD_HDLC);

	if (skb_is_nonlinear(skb)) {
		struct sk_buff *b;

		printk("ldl: non linear skb");
		b = skb_clone(skb, GFP_ATOMIC);
		kfree_skb(skb);
		if (b == NULL)
			return 0;
#ifdef HAVE_KFUNC_SKB_LINEARIZE_1_ARG
		if (skb_linearize(b)) {
			kfree_skb(b);
			return 0;
		}
#else
		if (skb_linearize(b, GFP_ATOMIC)) {
			kfree_skb(b);
			return 0;
		}
#endif
		skb = b;
	}
#if 1
	mblk_rtn.free_func = mblk_destructor;
	mblk_rtn.free_arg = (char *) skb;
	fr_len = skb->tail - skb->mac.raw;
	if ((dp = esballoc(skb->mac.raw - 2, fr_len + 2, BPRI_LO, &mblk_rtn)) != NULL) {
		dp->b_rptr = dp->b_wptr += 2;
		fr_ptr = dp->b_rptr;
		dp->b_wptr += fr_len;
		skb_get(skb);
	} else {
		fr_ptr = &fr_buf[0];
		fr_len = min(skb->end - skb->mac.raw, (ptrdiff_t) LDL_MAX_HDR_LEN);
		ASSERT(fr_len > 0);
		memcpy(fr_buf, skb->mac.raw, fr_len);
	}
#else
	if ((dp = allocb(2 + fr_len, BPRI_LO)) != NULL) {

		dp->b_rptr = dp->b_wptr += 2;
		dp->b_datap->db_type = M_DATA;
		memcpy(dp->b_wptr, skb->mac.raw, fr_len);
		fr_ptr = dp->b_rptr;
		dp->b_wptr += fr_len;
	} else {
		fr_ptr = &fr_buf[0];
		fr_len = min(skb->end - skb->mac.raw, LDL_MAX_HDR_LEN);
		ASSERT(fr_len > 0);
		memcpy(fr_buf, skb->mac.raw, fr_len);
	}
#endif

	if (ldl_debug_mask & LDL_DEBUG_RCV_FUNC)
		ldl_skbuff_dump("ldl_rcv_func: skb", skb, ldl_debug_mask & LDL_DEBUG_ALLDATA);
#ifdef KERNEL_2_1
	dev_kfree_skb(skb);
#else
	dev_kfree_skb(skb, FREE_WRITE);
#endif

	if (ldl_debug_mask & LDL_DEBUG_RCV_FUNC)
		ldl_bfr_dump("ldl_rcv_func", fr_ptr, fr_len, ldl_debug_mask & LDL_DEBUG_ALLDATA);

	read_lock(&((struct pt *) pt)->lock);
	ASSERT(((struct pt *) pt)->magic == PT_MAGIC);

	for (sap = ((struct pt *) pt)->listen; sap != NULL; sap = sap->next_listen) {
		dl = sap->dl;
		ASSERT(dl != NULL);
		ASSERT(dl->magic == DL_MAGIC);
		ASSERT(dl->rq != NULL);
		ASSERT(dl->ndev->dev == dev);
		if (dl->wantsframe(dl, fr_ptr, fr_len)) {
			if (last != NULL)
				dl_rcv_put(dp, last, 1);
			last = dl;
		}
	}

	if (last != NULL) {

		dl_rcv_put(dp, last, 0);
	} else {

		if (dp != NULL)
			freeb(dp);
	}
	read_unlock(&((struct pt *) pt)->lock);

	return 0;
}

STATIC void
send_uderror(struct dl *dl, char *addr, int addrlen, dl_ulong err, dl_ulong uerr)
{
	mblk_t *mp;
	dl_uderror_ind_t *nakp;

	if ((mp = allocb(DL_UDERROR_IND_SIZE + addrlen, BPRI_HI)) == NULL)
		return;
	mp->b_datap->db_type = M_PCPROTO;
	nakp = (dl_uderror_ind_t *) mp->b_wptr;
	nakp->dl_primitive = DL_UDERROR_IND;
	if (addrlen) {
		nakp->dl_dest_addr_length = addrlen;
		nakp->dl_dest_addr_offset = DL_UDERROR_IND_SIZE;
		memcpy(mp->b_rptr + DL_UDERROR_IND_SIZE, addr, addrlen);
	} else {
		nakp->dl_dest_addr_length = 0;
		nakp->dl_dest_addr_offset = 0;
	}
	nakp->dl_unix_errno = uerr;
	nakp->dl_errno = err;
	mp->b_wptr += DL_UDERROR_IND_SIZE + addrlen;

	putnext(dl->rq, mp);
	ginc(uderror_ind_cnt);
}

#define TXE_OUTSTATE	1
#define TXE_BADADDR	2
#define TXE_BADMTU	3
#define TXE_NOMEM	4
#define	TXE_NULL	5
#define	TXE_BADPRIO	6

STATIC int
tx_failed(struct dl *dl, mblk_t *mp, int err)
{
	dl_unitdata_req_t *reqp = (dl_unitdata_req_t *) mp->b_rptr;
	static dl_unitdata_req_t dummy;

	ginc(net_tx_fail_cnt);
	if (ldl_debug_mask & LDL_DEBUG_TX) {
		ldl_mp_dump("ldl: tx_failed", mp, ldl_debug_mask & LDL_DEBUG_ALLDATA);
		printk("ldl: tx_failed(%d)\n", err);
	}

	if (mp->b_datap->db_ref == 0) {
		printk("ldl: message with ref-count zero\n");
		return DONE;
	}

	if (mp->b_datap->db_type == M_DATA)
		reqp = &dummy;
	else
		ASSERT(reqp->dl_primitive == DL_UNITDATA_REQ);

	switch (err) {
	case TXE_OUTSTATE:
		send_uderror(dl, mp->b_rptr + reqp->dl_dest_addr_offset, reqp->dl_dest_addr_length,
			     DL_OUTSTATE, 0);
		break;
	case TXE_BADADDR:
		send_uderror(dl, mp->b_rptr + reqp->dl_dest_addr_offset, reqp->dl_dest_addr_length,
			     DL_BADADDR, 0);
		break;
	case TXE_BADMTU:
		send_uderror(dl, mp->b_rptr + reqp->dl_dest_addr_offset, reqp->dl_dest_addr_length,
			     DL_BADDATA, 0);
		break;
	case TXE_NOMEM:
	case TXE_NULL:
		send_uderror(dl, mp->b_rptr + reqp->dl_dest_addr_offset, reqp->dl_dest_addr_length,
			     DL_UNDELIVERABLE, 0);
		break;
	case TXE_BADPRIO:
		send_uderror(dl, mp->b_rptr + reqp->dl_dest_addr_offset, reqp->dl_dest_addr_length,
			     DL_UNSUPPORTED, 0);
		break;
	default:
		ASSERT(0);
	}

	freemsg(mp);
	return DONE;
}

#ifndef KERNEL_2_1
STATIC INLINE int
tx_pri(struct dl *dl, dl_unitdata_req_t * reqp)
{
	int p_min, p_max;

	if (reqp != NULL) {
		if (reqp->dl_priority.dl_min == DL_QOS_DONT_CARE)
			p_min = 100;
		else
			p_min = reqp->dl_priority.dl_min;
		if (reqp->dl_priority.dl_max == DL_QOS_DONT_CARE)
			p_max = 0;
		else
			p_max = reqp->dl_priority.dl_max;
		if (p_min < 0 || p_max < 0 || p_min > 100 || p_max > 100)
			return -2;

		if (p_max < dl->priority)
			p_max = dl->priority;
		if (p_max > p_min)
			p_max = p_min;

		p_min = pri_dlpi2netdevice(p_min);
		p_max = pri_dlpi2netdevice(p_max);
	} else {
		p_min = LDLPRI_LO;
		p_max = pri_dlpi2netdevice(dl->priority);
	}

	while (skb_queue_len(dl->ndev->dev->buffs + p_max) > dl->ndev->dev->tx_queue_len
	       && p_max <= p_min)
		++p_max;
	if (p_max > p_min) {
		pl_t psw;

		SPLSTR(psw);
		if (!dl->ndev->sleeping)
			ndev_wr_sleep(dl->ndev);
		SPLX(psw);
		return -1;
	}
	return p_max;
}
#else
STATIC INLINE int
tx_pri(struct dl *dl, dl_unitdata_req_t * reqp)
{
	int p_min, p_max;

	if (reqp != NULL) {
		if (reqp->dl_priority.dl_max == DL_QOS_DONT_CARE)
			p_max = dl->priority;
		else {
			p_max = reqp->dl_priority.dl_max;
			if (p_max < dl->priority)
				p_max = dl->priority;
		}
		if (reqp->dl_priority.dl_min != DL_QOS_DONT_CARE) {
			p_min = reqp->dl_priority.dl_min;
			if (p_max > p_min)
				p_max = p_min;
		}
	} else
		p_max = dl->priority;

	if (p_max < 0 || p_max > 100)
		return -2;

	return pri_dlpi2netdevice(p_max);
}
#endif

STATIC INLINE int
tx_func_proto(struct dl *dl, mblk_t *mp)
{
	dl_unitdata_req_t *reqp;
	mblk_t *dmp;
	struct sk_buff *skb;
	int pri, dlen;

	ginc(unitdata_req_cnt);
	ASSERT(mp->b_datap->db_type == M_PROTO || mp->b_datap->db_type == M_PCPROTO);
	ASSERT(dl->magic == DL_MAGIC);

	if (ldl_debug_mask & LDL_DEBUG_UDREQ)
		ldl_mp_data_dump("ldl_unitdata_req", mp, ldl_debug_mask & LDL_DEBUG_ALLDATA);

	if (dl->dlstate != DL_IDLE)
		return tx_failed(dl, mp, TXE_OUTSTATE);

	reqp = (dl_unitdata_req_t *) mp->b_rptr;
	ASSERT(reqp->dl_primitive == DL_UNITDATA_REQ);

	if ((dl->flags & LDLFLAG_RAW) != 0) {
		int rslt;

		dmp = mp->b_cont;
		ASSERT(dmp != NULL);
		if (dmp == NULL)
			return tx_failed(dl, mp, TXE_NULL);
		rslt = tx_func_raw(dl, dmp);
		if (rslt == DONE) {
			unlinkb(mp);
			freemsg(mp);
		}
		return rslt;
	}

	if (reqp->dl_dest_addr_length != dl->addr_len + dl->sap_len)
		return tx_failed(dl, mp, TXE_BADADDR);

	if ((dmp = mp->b_cont) != NULL) {
		ASSERT(dmp->b_datap->db_type == M_DATA);
		dlen = msgdsize(dmp);
		if (dlen > dl->mtu)
			return tx_failed(dl, mp, TXE_BADMTU);
	} else
		dlen = 0;

	if ((pri = tx_pri(dl, reqp)) < 0)
		return pri == -1 ? RETRY : tx_failed(dl, mp, TXE_BADPRIO);

	if ((skb = alloc_skb(dlen + dl->machdr_reserve, GFP_ATOMIC)) == NULL)
		return tx_failed(dl, mp, TXE_NOMEM);
#ifndef KERNEL_2_1
	skb->free = 1;
#endif

	ASSERT(dlen == 0 || dmp != NULL);

	skb_reserve(skb, dl->machdr_reserve);
#ifdef KERNEL_2_1
	skb->nh.raw = skb->data;
#endif
	if (dl->mkhdr(dl, (char *) reqp + reqp->dl_dest_addr_offset, dlen, skb) != 1) {
#ifdef KERNEL_2_1
		kfree_skb(skb);
#else
		kfree_skb(skb, FREE_WRITE);
#endif
		return tx_failed(dl, mp, TXE_BADADDR);
	}

	while (dmp) {
		void *p;
		int dlen;

		ASSERT(dmp->b_datap->db_type == M_DATA);

		dlen = dmp->b_wptr - dmp->b_rptr;
		ASSERT(dlen > 0);
		p = skb_put(skb, dlen);
		ASSERT(p != NULL);
		memcpy(p, dmp->b_rptr, dlen);
		dmp = dmp->b_cont;
	}
	ASSERT(skb->len == dlen + dl->machdr_len);

	if (ldl_debug_mask & LDL_DEBUG_TX)
		ldl_bfr_dump("ldl_tx_func", skb->data, skb->len,
			     ldl_debug_mask & LDL_DEBUG_ALLDATA);
#ifdef KERNEL_2_1
	skb->priority = pri;
	if (ndev_xmit(dl->ndev, skb) == RETRY)
		return RETRY;
#else
	if (ndev_xmit(dl->ndev, skb, pri) == RETRY)
		return RETRY;
#if 0
	atomic_add(skb->truesize, &ndev->wr_cur);
	(struct ndev *) skb->sk = dl->ndev;
	skb->dev = dl->ndev->dev;
	skb->free = 1;
	skb->arp = 1;
	skb->destructor = ndev_skb_destruct;
	dev_queue_xmit(skb, dl->ndev->dev, pri);
#endif
#endif
	freemsg(mp);
	ginc(net_tx_cnt);
	return DONE;
}

STATIC int
tx_func_raw(struct dl *dl, mblk_t *mp)
{
	mblk_t *omp = mp;
	struct sk_buff *skb;
	int pri, dlen;

	ASSERT(mp->b_datap->db_type == M_DATA);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT((dl->flags & LDLFLAG_RAW) != 0);

	if (dl->dlstate != DL_IDLE || !driver_started(dl->ndev->dev)
	    || netif_queue_stopped(dl->ndev->dev))
		return tx_failed(dl, mp, TXE_OUTSTATE);

	if ((pri = tx_pri(dl, NULL)) < 0)
		return pri == -1 ? RETRY : tx_failed(dl, mp, TXE_BADPRIO);

	dlen = msgdsize(mp);
	if (dlen > dl->mtu || dlen < dl->machdr_len)
		return tx_failed(dl, mp, TXE_BADMTU);

	if ((skb = alloc_skb(dlen, GFP_ATOMIC)) == NULL)
		return tx_failed(dl, mp, TXE_NOMEM);
#ifdef KERNEL_2_1
	skb->nh.raw = skb->data + dl->machdr_len;
#else
	skb->free = 1;
#endif

	while (mp) {
		void *p;
		int dlen;

		ASSERT(mp->b_datap->db_type == M_DATA);

		dlen = mp->b_wptr - mp->b_rptr;
		ASSERT(dlen > 0);
		p = skb_put(skb, dlen);
		ASSERT(p != NULL);
		memcpy(p, mp->b_rptr, dlen);
		mp = mp->b_cont;
	}
	ASSERT(skb->len == dlen);

	if (ldl_debug_mask & LDL_DEBUG_TX)
		ldl_bfr_dump("ldl_tx_func", skb->data, skb->len,
			     ldl_debug_mask & LDL_DEBUG_ALLDATA);
#ifdef KERNEL_2_1
	skb->priority = pri;
	if (ndev_xmit(dl->ndev, skb) == RETRY)
		return RETRY;
#else
	if (ndev_xmit(dl->ndev, skb, pri) == RETRY)
		return RETRY;
#endif
	freemsg(omp);
	ginc(net_tx_cnt);

	return DONE;
}

STATIC INLINE int
ws_info(struct dl *dl, mblk_t *mp)
{
	dl_info_ack_t *ackp;
	dl_qos_cl_sel1_t *qos;
	dl_qos_cl_range1_t *qos_range;
	int len;

	len = DL_INFO_ACK_SIZE + sizeof(dl_qos_cl_sel1_t) + sizeof(dl_qos_cl_range1_t);
	if (dl->dlstate != DL_UNATTACHED)
		len += dl->addr_len;
	if (dl->dlstate == DL_IDLE)
		len += dl->addr_len + dl->sap_len;

	if (!reuse_msg(mp, len)) {
		mblk_t *bp;

		if ((bp = allocb(len, BPRI_HI)) == NULL)
			return dl_bufcall(dl, mp, DL_ERROR_ACK_SIZE);
		freemsg(mp);
		mp = bp;
	}

	mp->b_datap->db_type = M_PCPROTO;
	ackp = (dl_info_ack_t *) mp->b_wptr;
	ackp->dl_primitive = DL_INFO_ACK;
	ackp->dl_min_sdu = MINDATA;
	ackp->dl_reserved = 0;
	ackp->dl_current_state = dl->dlstate;
	ackp->dl_sap_length = (dl->dlstate == DL_UNATTACHED) ? -2 : -dl->sap_len;
	ackp->dl_service_mode = DL_CLDLS;
	ackp->dl_provider_style = DL_STYLE2;
	ackp->dl_version = DL_VERSION_2;
	ackp->dl_growth = 0;
	mp->b_wptr += DL_INFO_ACK_SIZE;

	qos = (dl_qos_cl_sel1_t *) mp->b_wptr;
	qos->dl_qos_type = DL_QOS_CL_SEL1;
	qos->dl_trans_delay = DL_UNKNOWN;
	qos->dl_priority = dl->priority;
	qos->dl_protection = DL_NONE;
	qos->dl_residual_error = DL_UNKNOWN;
	ackp->dl_qos_length = sizeof(dl_qos_cl_sel1_t);
	ackp->dl_qos_offset = (char *) qos - (char *) ackp;
	mp->b_wptr += sizeof(dl_qos_cl_sel1_t);

	qos_range = (dl_qos_cl_range1_t *) mp->b_wptr;
	qos_range->dl_qos_type = DL_QOS_CL_RANGE1;
	qos_range->dl_trans_delay.dl_target_value = DL_UNKNOWN;
	qos_range->dl_trans_delay.dl_accept_value = DL_UNKNOWN;
	qos_range->dl_priority.dl_min = 100;
	qos_range->dl_priority.dl_max = 0;
	qos_range->dl_protection.dl_min = DL_NONE;
	qos_range->dl_protection.dl_max = DL_NONE;
	qos_range->dl_residual_error = DL_UNKNOWN;
	ackp->dl_qos_range_length = sizeof(dl_qos_cl_range1_t);
	ackp->dl_qos_range_offset = (char *) qos_range - (char *) ackp;
	mp->b_wptr += sizeof(dl_qos_cl_range1_t);

	if (dl->dlstate != DL_UNATTACHED) {
		int ofs = DL_INFO_ACK_SIZE + sizeof(dl_qos_cl_sel1_t) + sizeof(dl_qos_cl_range1_t);

		ASSERT(dl->ndev != NULL);
		ASSERT(dl->ndev->dev != NULL);

		ackp->dl_sap_length = -dl->sap_len;
		ackp->dl_max_sdu = dl->ndev->dev->mtu;

		switch (dl->ndev->dev->type) {
		case ARPHRD_ETHER:
			ackp->dl_mac_type = DL_ETHER;
			break;
		case ARPHRD_LOOPBACK:
			ackp->dl_mac_type = DL_LOOP;
			break;
		case ARPHRD_FDDI:
			ackp->dl_mac_type = DL_FDDI;
			break;
		case ARPHRD_IEEE802:
#if defined(ARPHRD_IEEE802_TR)
		case ARPHRD_IEEE802_TR:
#endif
			ackp->dl_mac_type = DL_TPR;
			break;
		case ARPHRD_HDLC:
			ackp->dl_mac_type = DL_HDLC;
			break;
		default:
			ackp->dl_mac_type = DL_OTHER;
			break;
		}

		ackp->dl_brdcst_addr_offset = ofs;
		ackp->dl_brdcst_addr_length = dl->addr_len;
		memcpy(mp->b_wptr, dl->ndev->dev->broadcast, dl->addr_len);
		mp->b_wptr += dl->addr_len;

		if (dl->dlstate == DL_IDLE) {
			ASSERT(dl->sap != NULL);
			ofs += dl->addr_len;
			ackp->dl_addr_offset = ofs;
			ackp->dl_addr_length = dl->addr_len + dl->sap_len;
			memcpy(mp->b_wptr, dl->ndev->dev->dev_addr, dl->addr_len);
			mp->b_wptr += dl->addr_len;
			if (dl->framing == LDL_FRAME_EII || dl->framing == LDL_FRAME_SNAP)
				*(unsigned short *) mp->b_wptr =
				    ntohs(*(unsigned short *) &dl->sap->sap.sap[0]);
			else
				memcpy(mp->b_wptr, &dl->sap->sap.sap[0], dl->sap_len);
			mp->b_wptr += dl->sap_len;
			ackp->dl_max_sdu = dl->ndev->dev->mtu;
		} else {
			if ((dl->flags & LDLFLAG_PEDANTIC_STANDARD) != 0)
				ackp->dl_sap_length = 0;
			ackp->dl_addr_length = 0;
			ackp->dl_addr_offset = 0;
		}
	} else {
		ackp->dl_sap_length = 0;
		ackp->dl_mac_type = DL_OTHER;
		ackp->dl_max_sdu = MAXDATA;
		ackp->dl_addr_length = 0;
		ackp->dl_addr_offset = 0;
		ackp->dl_brdcst_addr_length = 0;
		ackp->dl_brdcst_addr_offset = 0;
	}

	putnext(dl->rq, mp);

	return DONE;
}

STATIC INLINE int
ws_phys_addr(struct dl *dl, mblk_t *mp)
{
	dl_phys_addr_req_t *reqp;
	dl_phys_addr_ack_t *ackp;
	int len;

	if (dl->dlstate == DL_UNATTACHED)
		return reply_error_ack(dl, mp, DL_PHYS_ADDR_REQ, DL_OUTSTATE, 0);

	reqp = (dl_phys_addr_req_t *) mp->b_rptr;
	switch (reqp->dl_addr_type) {
	case DL_CURR_PHYS_ADDR:
		len = DL_PHYS_ADDR_ACK_SIZE + dl->addr_len;
		if (!reuse_msg(mp, len)) {
			mblk_t *bp;

			if ((bp = allocb(len, BPRI_HI)) == NULL)
				return dl_bufcall(dl, mp, DL_ERROR_ACK_SIZE);
			freemsg(mp);
			mp = bp;
		}

		mp->b_datap->db_type = M_PCPROTO;
		ackp = (dl_phys_addr_ack_t *) mp->b_wptr;
		ackp->dl_primitive = DL_PHYS_ADDR_ACK;
		ackp->dl_addr_length = dl->addr_len;
		ackp->dl_addr_offset = DL_PHYS_ADDR_ACK_SIZE;
		mp->b_wptr += DL_PHYS_ADDR_ACK_SIZE;
		memcpy(mp->b_wptr, dl->ndev->dev->dev_addr, dl->addr_len);
		mp->b_wptr += dl->addr_len;
		putnext(dl->rq, mp);
		return DONE;

	case DL_FACT_PHYS_ADDR:

	default:
		return reply_error_ack(dl, mp, DL_PHYS_ADDR_REQ, DL_NOTSUPPORTED, 0);
	}
}

#if 0
STATIC INLINE int
ws_set_phys_addr(struct dl *dl, mblk_t *mp)
{
	dl_set_phys_addr_req_t *reqp;
	int err;
	mblk_t *ack_mp;
	pl_t psw;

	if (dl->dlstate == DL_UNATTACHED)
		return reply_error_ack(dl, mp, DL_SET_PHYS_ADDR_REQ, DL_OUTSTATE, 0);
	if (dl->ndev->dev->set_mac_address == NULL)
		return reply_error_ack(dl, mp, DL_SET_PHYS_ADDR_REQ, DL_NOTSUPPORTED, 0);

	reqp = (dl_set_phys_addr_req_t *) mp->b_rptr;

	if (reqp->dl_addr_length != dl->addr_len)
		return reply_error_ack(dl, mp, DL_SET_PHYS_ADDR_REQ, DL_BADADDR, 0);

	ASSERT(DL_ERROR_ACK_SIZE >= DL_OK_ACK_SIZE);
	if ((ack_mp = allocb(DL_ERROR_ACK_SIZE, BPRI_HI)) == NULL)
		return dl_bufcall(dl, ack_mp, DL_ERROR_ACK_SIZE);

	SPLSTR(psw);
	dl->flags |= LDLFLAG_SET_ADDR;
	SPLX(psw);
	err = dev_close(dl->ndev->dev);
	SPLSTR(psw);
	dl->flags &= ~LDLFLAG_SET_ADDR;
	SPLX(psw);
	if (err) {
		freemsg(mp);
		make_dl_error_ack(ack_mp, DL_SET_PHYS_ADDR_REQ, DL_SYSERR, -err);
		putnext(dl->rq, ack_mp);
		ginc(error_ack_cnt);
		return DONE;
	}
	dl->ndev->dev->set_mac_address(dl->ndev->dev, mp->b_rptr + reqp->dl_addr_offset);
	freemsg(mp);
	err = dev_open(dl->ndev->dev);
	if (err) {

		freemsg(ack_mp);
		SPLSTR(psw);
		hangup_set(dl);
		SPLX(psw);
		hangup_do(dl);
		return DONE;
	}
	ASSERT((dl->ndev->dev->flags & IFF_UP) != 0);
	ASSERT((dl->ndev->dev->flags & IFF_RUNNING) != 0);

	make_dl_ok_ack(ack_mp, DL_SET_PHYS_ADDR_REQ);
	putnext(dl->rq, ack_mp);
	ginc(ok_ack_cnt);

	return DONE;
}
#endif

STATIC INLINE int
ws_attach(struct dl *dl, mblk_t *mp)
{
	struct ndev *ndev;
	struct ldldev *dev;
	dl_attach_req_t *reqp;
	dl_ulong framing, ppa;
	pl_t psw;

	ginc(attach_req_cnt);
	if (dl->dlstate != DL_UNATTACHED)
		return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_OUTSTATE, 0);

	reqp = (dl_attach_req_t *) mp->b_rptr;

	ppa = reqp->dl_ppa & ~LDL_FRAME_MASK;
	framing = reqp->dl_ppa & LDL_FRAME_MASK;
	if (framing != LDL_FRAME_EII && framing != LDL_FRAME_802_2 && framing != LDL_FRAME_SNAP
	    && framing != LDL_FRAME_802_3 && framing != LDL_FRAME_RAW_LLC)
		return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);

	SPLSTR(psw);

	ndev = ndev_get(ppa);
	if (ndev == NULL) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
	}
	dev = ndev->dev;
	ASSERT(dev != NULL);

	switch (dev->type) {
	case ARPHRD_ETHER:
	case ARPHRD_LOOPBACK:
		if (framing != LDL_FRAME_EII && framing != LDL_FRAME_802_2
		    && framing != LDL_FRAME_SNAP && framing != LDL_FRAME_802_3
		    && framing != LDL_FRAME_RAW_LLC) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
		}
		break;
	case ARPHRD_FDDI:
		if (framing == 0)
			framing = LDL_FRAME_SNAP;
		if (framing != LDL_FRAME_802_2 && framing != LDL_FRAME_SNAP
		    && framing != LDL_FRAME_RAW_LLC) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
		}
		break;
	case ARPHRD_IEEE802:
#if defined(ARPHRD_IEEE802_TR)
	case ARPHRD_IEEE802_TR:
#endif
		if (framing != LDL_FRAME_802_2 && framing != LDL_FRAME_SNAP
		    && framing != LDL_FRAME_RAW_LLC) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
		}
		break;
	case ARPHRD_HDLC:
		if (framing != LDL_FRAME_RAW_LLC) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
		}
		break;
	default:
		printk("ldl: ws_attach: Unknown dev->type (%d)\n", (int) (dev->type));
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
	}

	if (do_ok_ack(dl, &mp, DL_ATTACH_REQ) == RETRY) {
		SPLX(psw);
		return RETRY;
	} else if (mp == NULL) {
		SPLX(psw);
		return DONE;
	}

	dl->dlstate = DL_UNBOUND;
	dl->addr_len = ETH_ALEN;
	dl->framing = framing;
	switch (dl->framing) {
	case LDL_FRAME_EII:
		dl->mtu = 1500;
		dl->machdr_len = 14;
		dl->machdr_reserve = 16;
		dl->sap_len = 2;
		dl->wantsframe = eth_ii_want;
		dl->rcvind = eth_ii_rcvind;
		dl->mkhdr = eth_ii_mkhdr;
		break;
	case LDL_FRAME_SNAP:
		dl->oui[0] = dl->oui[1] = dl->oui[2] = '\0';
		dl->sap_len = 2;
		if (dev->type == ARPHRD_FDDI) {
			dl->mtu = 4470;
			dl->machdr_len = 21;
			dl->machdr_reserve = 24;
			dl->wantsframe = fddi_snap_want;
			dl->rcvind = fddi_snap_rcvind;
			dl->mkhdr = fddi_snap_mkhdr;
		} else {
			dl->mtu = 1492;
			dl->machdr_len = 22;
			dl->machdr_reserve = 24;
			dl->wantsframe = eth_snap_want;
			dl->rcvind = eth_snap_rcvind;
			dl->mkhdr = eth_snap_mkhdr;
		}
		break;
	case LDL_FRAME_802_2:
		dl->sap_len = 1;
		switch (dev->type) {
		case ARPHRD_IEEE802:
#if defined(ARPHRD_IEEE802_TR)
		case ARPHRD_IEEE802_TR:
#endif
		      token_ring_case:
			dl->sap_len = 1;
			dl->mtu = 1514;
			dl->machdr_len = sizeof(tr_hdr_t) - 2 + sizeof(tr_llc_frm_hdr_t);
			dl->machdr_reserve = dl->machdr_len + 64;
			dl->wantsframe = tr_8022_want;
			dl->rcvind = tr_8022_rcvind;
			dl->mkhdr = tr_8022_mkhdr;
			break;
		case ARPHRD_FDDI:
			dl->mtu = 4475;
			dl->machdr_len = 16;
			dl->machdr_reserve = 24;
			dl->wantsframe = fddi_8022_want;
			dl->rcvind = fddi_8022_rcvind;
			dl->mkhdr = fddi_8022_mkhdr;
			break;
		case ARPHRD_ETHER:
		default:
			dl->mtu = 1497;
			dl->machdr_len = 17;
			dl->machdr_reserve = 24;
			dl->wantsframe = eth_8022_want;
			dl->rcvind = eth_8022_rcvind;
			dl->mkhdr = eth_8022_mkhdr;
			break;
		}
		break;
	case LDL_FRAME_RAW_LLC:
		dl->sap_len = 1;
		dl->flags |= LDLFLAG_RAW;
		switch (dev->type) {
		case ARPHRD_IEEE802:
#if defined(ARPHRD_IEEE802_TR)
		case ARPHRD_IEEE802_TR:
#endif
			goto token_ring_case;

		case ARPHRD_FDDI:

			ASSERT(0);
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
		case ARPHRD_HDLC:
			if (dev->mtu)
				dl->mtu = dev->mtu;
			else
				dl->mtu = 1500;
			dl->machdr_len = 0;
			dl->machdr_reserve = 0;
			dl->wantsframe = hdlc_raw_want;
			dl->rcvind = hdlc_raw_rcvind;
			dl->mkhdr = hdlc_raw_mkhdr;
			break;

		case ARPHRD_ETHER:
		default:
			dl->mtu = 1500;
			dl->machdr_len = 14;
			dl->machdr_reserve = 16;
			dl->wantsframe = eth_raw8022_want;
			dl->rcvind = eth_raw8022_rcvind;
			dl->mkhdr = eth_raw8022_mkhdr;
			break;
		}
		break;
	case LDL_FRAME_802_3:
		dl->mtu = 1500;
		dl->machdr_len = 14;
		dl->machdr_reserve = 16;
		dl->sap_len = 0;
		dl->wantsframe = ipx_8023_want;
		dl->rcvind = ipx_8023_rcvind;
		dl->mkhdr = ipx_8023_mkhdr;
		break;
	default:
		ASSERT(0);
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_ATTACH_REQ, DL_BADPPA, 0);
	}

	ndev_attach(ndev, dl);

	SPLX(psw);

	if (ldl_debug_mask & LDL_DEBUG_ATTACH)
		printk("ldl: ws_attach: " "ppa=%lx dev=\"%s\" framing=%s\n", (long) ppa, dev->name,
		       ldl_framing_type(framing));

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);

	return DONE;
}

STATIC INLINE int
ws_detach(struct dl *dl, mblk_t *mp)
{
	pl_t psw;

	ginc(detach_req_cnt);
	if (dl->dlstate != DL_UNBOUND)
		return reply_error_ack(dl, mp, DL_DETACH_REQ, DL_OUTSTATE, 0);

	if (do_ok_ack(dl, &mp, DL_DETACH_REQ) == RETRY)
		return RETRY;
	else if (mp == NULL)
		return DONE;

	SPLSTR(psw);
	ASSERT(dl->dlstate == DL_UNBOUND);
	dl->dlstate = DL_UNATTACHED;
	dl->addr_len = 0;
	ndev_release(dl);
	SPLX(psw);

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);

	return DONE;
}

STATIC INLINE int
ws_bind(struct dl *dl, mblk_t *mp)
{
	dl_bind_req_t *reqp;
	dl_bind_ack_t *ackp;
	sap_t dlsap;
	unsigned short saptype;
	pl_t psw;
	int len;

	ginc(bind_req_cnt);
	SPLSTR(psw);
	if (dl->dlstate != DL_UNBOUND) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_BIND_REQ, DL_OUTSTATE, 0);
	}

	reqp = (dl_bind_req_t *) mp->b_rptr;

	if (reqp->dl_service_mode != DL_CLDLS) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_BIND_REQ, DL_UNSUPPORTED, 0);
	}
	if (reqp->dl_xidtest_flg != 0) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_BIND_REQ, DL_NOAUTO, 0);
	}

	switch (dl->framing) {
	case LDL_FRAME_SNAP:
		if (reqp->dl_sap <= 0xffff) {
			*(unsigned short *) &dlsap.sap[0] = htons((unsigned short) reqp->dl_sap);

			saptype = ETH_P_802_2;
			break;
		} else {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_BIND_REQ, DL_BADADDR, 0);
		}
	case LDL_FRAME_EII:
		if (reqp->dl_sap <= 0xffff) {
			*(unsigned short *) &dlsap.sap[0] = htons((unsigned short) reqp->dl_sap);

			saptype = (unsigned short) reqp->dl_sap;
			break;
		} else {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_BIND_REQ, DL_BADADDR, 0);
		}
	case LDL_FRAME_802_2:
	case LDL_FRAME_RAW_LLC:
		if (reqp->dl_sap <= 0xff) {
			dlsap.sap[0] = reqp->dl_sap;
			saptype = ETH_P_802_2;
			break;
		} else {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_BIND_REQ, DL_BADADDR, 0);
		}
	case LDL_FRAME_802_3:
		if (reqp->dl_sap == 0) {

			saptype = ETH_P_802_3;
			break;
		} else {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_BIND_REQ, DL_BADADDR, 0);
		}
	default:
		ASSERT(0);
		return reply_error_ack(dl, mp, DL_BIND_REQ, DL_BADADDR, 0);
	}

	if (sap_create(dl, dlsap, saptype) < 0) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_BIND_REQ, DL_SYSERR, ENOMEM);
	}

	len = DL_BIND_ACK_SIZE + dl->addr_len + dl->sap_len;
	if (!reuse_msg(mp, len)) {
		mblk_t *bp;

		if ((bp = allocb(len, BPRI_HI)) == NULL) {
			SPLX(psw);
			return dl_bufcall(dl, mp, len);
		}
		freemsg(mp);
		mp = bp;
	}

	mp->b_datap->db_type = M_PCPROTO;
	ackp = (dl_bind_ack_t *) mp->b_wptr;
	ackp->dl_primitive = DL_BIND_ACK;
	ackp->dl_sap = reqp->dl_sap;
	ackp->dl_addr_length = dl->addr_len + dl->sap_len;
	ackp->dl_addr_offset = DL_BIND_ACK_SIZE;
	ackp->dl_max_conind = 0;
	ackp->dl_xidtest_flg = 0;
	mp->b_wptr += DL_BIND_ACK_SIZE;
	memcpy(mp->b_wptr, dl->ndev->dev->dev_addr, dl->addr_len);
	mp->b_wptr += dl->addr_len;
	if (dl->framing == LDL_FRAME_EII || dl->framing == LDL_FRAME_SNAP)
		*(unsigned short *) mp->b_wptr = ntohs(*(unsigned short *) &dlsap.sap[0]);
	else
		memcpy(mp->b_wptr, &dl->sap->sap.sap[0], dl->sap_len);
	mp->b_wptr += dl->sap_len;

	dl->dlstate = DL_IDLE;
	SPLX(psw);

	if (ldl_debug_mask & LDL_DEBUG_BIND)
		printk("ldl: ws_bind: " "dl_sap=%lx framing=%s pkt-type=%s\n", (long) reqp->dl_sap,
		       ldl_framing_type(dl->framing), ldl_pkt_type(saptype));

	putnext(dl->rq, mp);

	return DONE;
}

STATIC INLINE int
ws_unbind(struct dl *dl, mblk_t *mp)
{
	pl_t psw;

	ginc(unbind_req_cnt);
	SPLSTR(psw);
	if (dl->dlstate != DL_IDLE) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_UNBIND_REQ, DL_OUTSTATE, 0);
	}

	if (do_ok_ack(dl, &mp, DL_UNBIND_REQ) == RETRY) {
		SPLX(psw);
		return RETRY;
	} else if (mp == NULL) {
		SPLX(psw);
		return DONE;
	}

	dl->dlstate = DL_UNBIND_PENDING;
	SPLX(psw);

	sap_destroy_all(dl);

	if (putctl1(dl->rq->q_next, M_FLUSH, FLUSHRW) == 0)
		printk("ldl: cannot flush stream on unbind\n");

	SPLSTR(psw);
	ASSERT(dl->dlstate == DL_UNBIND_PENDING);
	dl->dlstate = DL_UNBOUND;
	if ((dl->flags & LDLFLAG_HANGUP) != 0) {
		SPLX(psw);
		freemsg(mp);
		hangup_do(dl);
		return DONE;
	}
	SPLX(psw);

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);

	return DONE;
}

STATIC INLINE int
ws_subs_bind(struct dl *dl, mblk_t *mp)
{
	dl_subs_bind_req_t *reqp;
	dl_subs_bind_ack_t *ackp;
	int len;
	pl_t psw;

	ginc(subs_bind_req_cnt);
	SPLSTR(psw);
	if (dl->dlstate != DL_IDLE) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_OUTSTATE, 0);
	}
	if (dl->framing == LDL_FRAME_802_3) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_UNSUPPORTED, 0);
	}

	reqp = (dl_subs_bind_req_t *) mp->b_rptr;

	if (reqp->dl_subs_bind_class == DL_HIERARCHICAL_BIND) {
		unsigned char oui[3];

		if (dl->framing != LDL_FRAME_SNAP) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_UNSUPPORTED, 0);
		}
		if (reqp->dl_subs_sap_length != 3) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_BADADDR, 0);
		}
		ASSERT(dl->sap_len == 2);
		memcpy(oui, mp->b_rptr + reqp->dl_subs_sap_offset, 3);

		len = DL_SUBS_BIND_ACK_SIZE + 3;
		if (!reuse_msg(mp, len)) {
			mblk_t *bp;

			if ((bp = allocb(len, BPRI_HI)) == NULL) {
				SPLX(psw);
				return dl_bufcall(dl, mp, len);
			}
			freemsg(mp);
			mp = bp;
		}

		memcpy(dl->oui, oui, 3);

		SPLX(psw);

		mp->b_datap->db_type = M_PCPROTO;
		ackp = (dl_subs_bind_ack_t *) mp->b_wptr;
		ackp->dl_primitive = DL_SUBS_BIND_ACK;
		ackp->dl_subs_sap_length = 3;
		ackp->dl_subs_sap_offset = DL_SUBS_BIND_ACK_SIZE;
		mp->b_wptr += DL_SUBS_BIND_ACK_SIZE;
		memcpy(mp->b_wptr, dl->oui, 3);
		mp->b_wptr += 3;

		putnext(dl->rq, mp);
		return DONE;
	} else if (reqp->dl_subs_bind_class == DL_PEER_BIND) {
		sap_t dlsap;
		dl_ushort saptype;
		int saplen = reqp->dl_subs_sap_length;
		unsigned char *sapptr = mp->b_rptr + reqp->dl_subs_sap_offset;

		switch (dl->framing) {
		case LDL_FRAME_EII:
			if (saplen != 2) {
				SPLX(psw);
				return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_BADADDR, 0);
			}
			*(unsigned short *) &dlsap.sap[0] = htons(*(unsigned short *) sapptr);
			saptype = *(unsigned short *) sapptr;
			break;
		case LDL_FRAME_802_2:
		case LDL_FRAME_RAW_LLC:
			if (saplen != 1) {
				SPLX(psw);
				return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_BADADDR, 0);
			}
			dlsap.sap[0] = *sapptr;
			saptype = ETH_P_802_2;

			break;
		case LDL_FRAME_SNAP:
			if (saplen != 2) {
				SPLX(psw);
				return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_BADADDR, 0);
			}
			*(unsigned short *) &dlsap.sap[0] = htons(*(unsigned short *) sapptr);
			saptype = ETH_P_802_2;
			break;
		case LDL_FRAME_802_3:
		default:
			ASSERT(0);
			freemsg(mp);
			return DONE;
		}

		len = DL_SUBS_BIND_ACK_SIZE + saplen;
		if (!reuse_msg(mp, len)) {
			mblk_t *bp;

			if ((bp = allocb(len, BPRI_HI)) == NULL) {
				SPLX(psw);
				return dl_bufcall(dl, mp, len);
			}
			freemsg(mp);
			mp = bp;
		}

		if (sap_create(dl, dlsap, saptype) < 0) {
			SPLX(psw);
			return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_TOOMANY, 0);
		}
		SPLX(psw);

		mp->b_datap->db_type = M_PCPROTO;
		ackp = (dl_subs_bind_ack_t *) mp->b_wptr;
		ackp->dl_primitive = DL_SUBS_BIND_ACK;
		ackp->dl_subs_sap_length = saplen;
		ackp->dl_subs_sap_offset = DL_SUBS_BIND_ACK_SIZE;
		mp->b_wptr += DL_SUBS_BIND_ACK_SIZE;
		if (dl->framing == LDL_FRAME_EII || dl->framing == LDL_FRAME_SNAP)
			*(unsigned short *) mp->b_wptr = ntohs(*(unsigned short *) &dlsap.sap[0]);
		else
			memcpy(mp->b_wptr, dlsap.sap, saplen);
		mp->b_wptr += saplen;

		putnext(dl->rq, mp);
		return DONE;
	} else {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_SUBS_BIND_REQ, DL_UNSUPPORTED, 0);
	}
}

STATIC INLINE int
ws_subs_unbind(struct dl *dl, mblk_t *mp)
{
	dl_subs_unbind_req_t *reqp;
	int saplen;
	unsigned char *sapptr;
	struct sap *sap;
	sap_t dlsap;
	pl_t psw;

	ginc(subs_unbind_req_cnt);
	if (dl->dlstate != DL_IDLE)
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_OUTSTATE, 0);

	if (dl->subs != NULL)
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);

	reqp = (dl_subs_unbind_req_t *) mp->b_rptr;
	saplen = reqp->dl_subs_sap_length;
	sapptr = mp->b_rptr + reqp->dl_subs_sap_offset;

	switch (dl->framing) {
	case LDL_FRAME_EII:
	case LDL_FRAME_SNAP:
		ASSERT(dl->sap_len == 2);
		if (saplen != 2)
			return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);
		*(unsigned short *) &dlsap.sap[0] = htons(*(unsigned short *) sapptr);
		break;
	case LDL_FRAME_802_2:
	case LDL_FRAME_RAW_LLC:
		ASSERT(dl->sap_len == 1);
		if (saplen != 1)
			return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);
		dlsap.sap[0] = *sapptr;
		break;
	case LDL_FRAME_802_3:
	default:
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);
	}

	for (sap = dl->subs; sap != NULL && memcmp(dlsap.sap, sap->sap.sap, dl->sap_len);
	     sap = sap->next_sap) ;

	if (sap == NULL)
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);

	ASSERT(!memcmp(dlsap.sap, sap->sap.sap, dl->sap_len));
	ASSERT(sap->magic == SAP_MAGIC);
	ASSERT(sap->dl == dl);

	SPLSTR(psw);
	if (dl->dlstate != DL_IDLE) {
		SPLX(psw);
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_OUTSTATE, 0);
	}
	dl->dlstate = DL_SUBS_UNBIND_PND;
	SPLX(psw);

	if (do_ok_ack(dl, &mp, DL_SUBS_UNBIND_REQ) == RETRY)
		return RETRY;
	else if (mp == NULL)
		return DONE;

	if (sap_destroy(dl, sap) < 0) {
		dl->dlstate = DL_IDLE;
		return reply_error_ack(dl, mp, DL_SUBS_UNBIND_REQ, DL_BADADDR, 0);
	}

	SPLSTR(psw);
	ASSERT(dl->dlstate == DL_SUBS_UNBIND_PND);
	dl->dlstate = DL_IDLE;
	if ((dl->flags & LDLFLAG_HANGUP) != 0) {
		hangup_set(dl);
		SPLX(psw);
		freemsg(mp);
		hangup_do(dl);
		return DONE;
	}
	SPLX(psw);

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);

	return DONE;
}

STATIC INLINE int
ws_udqos(struct dl *dl, mblk_t *mp)
{
	dl_udqos_req_t *reqp;
	dl_qos_cl_sel1_t *qos;
	long priority;

	ginc(udqos_req_cnt);
	if (dl->dlstate != DL_IDLE)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_OUTSTATE, 0);

	reqp = (dl_udqos_req_t *) mp->b_rptr;
	if (reqp->dl_qos_length < sizeof(dl_qos_cl_sel1_t)
	    || reqp->dl_qos_offset < DL_UDQOS_REQ_SIZE)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSTYPE, 0);

	qos = (dl_qos_cl_sel1_t *) (mp->b_rptr + reqp->dl_qos_offset);
	if (qos->dl_qos_type != DL_QOS_CL_SEL1)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSTYPE, 0);
	if (qos->dl_trans_delay >= 0)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSPARAM, 0);
	if (qos->dl_protection >= 0 && qos->dl_protection != DL_NONE)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSPARAM, 0);
	if (qos->dl_residual_error >= 0)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSPARAM, 0);
	if (qos->dl_priority > 100)
		return reply_error_ack(dl, mp, DL_UDQOS_REQ, DL_BADQOSPARAM, 0);

	if (qos->dl_priority >= 0 && qos->dl_priority <= 100)
		priority = qos->dl_priority;
	else
		priority = dl->priority;

	if (do_ok_ack(dl, &mp, DL_UDQOS_REQ) == RETRY)
		return RETRY;
	else if (mp == NULL)
		return DONE;

	dl->priority = priority;

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);

	return DONE;
}

STATIC INLINE int
ws_promiscon(struct dl *dl, mblk_t *mp)
{
	dl_promiscon_req_t *reqp;

	if (dl->dlstate != DL_UNBOUND && dl->dlstate != DL_IDLE)
		return reply_error_ack(dl, mp, DL_PROMISCON_REQ, DL_OUTSTATE, 0);

	if (dl->framing != LDL_FRAME_802_2 && dl->framing != LDL_FRAME_RAW_LLC)
		return reply_error_ack(dl, mp, DL_PROMISCON_REQ, DL_NOTSUPPORTED, 0);

	reqp = (dl_promiscon_req_t *) mp->b_rptr;
	switch (reqp->dl_level) {
	case DL_PROMISC_SAP:
		if (do_ok_ack(dl, &mp, DL_PROMISCON_REQ) == RETRY)
			return RETRY;
		if (mp != NULL) {
			dl->flags |= LDLFLAG_PROMISC_SAP;
			putnext(dl->rq, mp);
			ginc(ok_ack_cnt);
		}
		return DONE;
	case DL_PROMISC_PHYS:

	case DL_PROMISC_MULTI:

	default:
		return reply_error_ack(dl, mp, DL_PROMISCON_REQ, DL_NOTSUPPORTED, 0);
	}
}

STATIC INLINE int
ws_promiscoff(struct dl *dl, mblk_t *mp)
{
	dl_promiscoff_req_t *reqp;

	if (dl->dlstate != DL_UNBOUND && dl->dlstate != DL_IDLE)
		return reply_error_ack(dl, mp, DL_PROMISCOFF_REQ, DL_OUTSTATE, 0);

	if (dl->framing != LDL_FRAME_802_2 && dl->framing != LDL_FRAME_RAW_LLC)
		return reply_error_ack(dl, mp, DL_PROMISCOFF_REQ, DL_NOTSUPPORTED, 0);

	reqp = (dl_promiscoff_req_t *) mp->b_rptr;
	switch (reqp->dl_level) {
	case DL_PROMISC_SAP:
		if (do_ok_ack(dl, &mp, DL_PROMISCOFF_REQ) == RETRY)
			return RETRY;
		if (mp != NULL) {
			dl->flags &= ~LDLFLAG_PROMISC_SAP;
			putnext(dl->rq, mp);
			ginc(ok_ack_cnt);
		}
		return DONE;
	case DL_PROMISC_PHYS:

	case DL_PROMISC_MULTI:

	default:
		return reply_error_ack(dl, mp, DL_PROMISCOFF_REQ, DL_NOTSUPPORTED, 0);
	}
}

STATIC INLINE int
ws_enabmulti(struct dl *dl, mblk_t *mp)
{
	dl_enabmulti_req_t *reqp;

	if (dl->dlstate == DL_UNATTACHED)
		return reply_error_ack(dl, mp, DL_ENABMULTI_REQ, DL_OUTSTATE, 0);

	reqp = (dl_enabmulti_req_t *) mp->b_rptr;

	switch (dev_mc_add
		(dl->ndev->dev, mp->b_rptr + reqp->dl_addr_offset, reqp->dl_addr_length, 0)) {
	case 0:
		if (do_ok_ack(dl, &mp, DL_ENABMULTI_REQ) == RETRY)
			return RETRY;
		else if (mp == NULL)
			return DONE;
		break;

	default:
		return reply_error_ack(dl, mp, DL_ENABMULTI_REQ, DL_NOTSUPPORTED, 0);
	}

	putnext(dl->rq, mp);
	ginc(ok_ack_cnt);
	return DONE;
}

STATIC INLINE int
ws_disabmulti(struct dl *dl, mblk_t *mp)
{
	dl_disabmulti_req_t *reqp;

	if (dl->dlstate == DL_UNATTACHED)
		return reply_error_ack(dl, mp, DL_DISABMULTI_REQ, DL_OUTSTATE, 0);

	reqp = (dl_disabmulti_req_t *) mp->b_rptr;
	return 0;
}

STATIC INLINE int
ws_error(struct dl *dl, mblk_t *mp, dl_ulong primitive, dl_ulong err)
{
	if (reply_error_ack(dl, mp, primitive, err, 0) == RETRY)
		return RETRY;

	return DONE;
}

STATIC INLINE int
ioc_nak(struct dl *dl, mblk_t *mp)
{
	mp->b_datap->db_type = M_IOCNAK;
	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC INLINE int
ioc_setflags(struct dl *dl, struct iocblk *iocp, mblk_t *mp)
{
	caddr_t uaddr = NULL;
	size_t usize;
	pl_t psw;

	ASSERT(iocp->ioc_cmd == LDL_SETFLAGS);

	if (mp->b_cont == NULL || mp->b_cont->b_datap->db_type != M_DATA)
		return ioc_nak(dl, mp);

#ifdef WITH_32BIT_CONVERSION
	if (iocp->ioc_flag == IOC_ILP32) {
		struct ldl_flags_ioctl32 *flg;

		usize = sizeof(*flg);
		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t) (unsigned long) (uint32_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		} else {
			if (iocp->ioc_count != usize)
				return ioc_nak(dl, mp);
			flg = (typeof(flg)) mp->b_cont->b_rptr;
			flg->mask &= ~LDLFLAG_PRIVATE;
			SPLSTR(psw);
			dl->flags = (dl->flags & ~flg->mask) | (flg->flags & flg->mask);
			SPLX(psw);
			flg->flags = dl->flags;
		}
	} else
#endif
	{
		struct ldl_flags_ioctl *flg;

		usize = sizeof(*flg);
		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		} else {
			if (iocp->ioc_count != usize)
				return ioc_nak(dl, mp);
			flg = (typeof(flg)) mp->b_cont->b_rptr;
			flg->mask &= ~LDLFLAG_PRIVATE;
			SPLSTR(psw);
			dl->flags = (dl->flags & ~flg->mask) | (flg->flags & flg->mask);
			SPLX(psw);
			flg->flags = dl->flags;
		}
	}

	if (iocp->ioc_count == TRANSPARENT) {
		struct copyreq *cq = (typeof(cq)) iocp;

		mp->b_datap->db_type = M_COPYOUT;
		cq->cq_addr = uaddr;
		cq->cq_size = usize;
	} else {

		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
	}
	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC INLINE int
ioc_findppa(struct dl *dl, struct iocblk *iocp, mblk_t *mp)
{
	struct ldldev *dev;
	dl_ulong ppa;
	mblk_t *dp;

	ASSERT(iocp->ioc_cmd == LDL_FINDPPA);

	if (mp->b_cont == NULL || mp->b_cont->b_datap->db_type != M_DATA)
		return ioc_nak(dl, mp);

	if (iocp->ioc_count == TRANSPARENT)
		return ioc_nak(dl, mp);

	if (iocp->ioc_count <= 0)
		return ioc_nak(dl, mp);

	dp = mp->b_cont;

	for (dev = dev_base, ppa = 0; dev != NULL; dev = dev->next, ++ppa)
		if (!strcmp(dev->name, dp->b_rptr))
			break;
	if (dev == NULL)
		return ioc_nak(dl, mp);

	dp->b_wptr = dp->b_rptr + sizeof(dl_ulong);
	*(dl_ulong *) dp->b_rptr = ppa;

	mp->b_datap->db_type = M_IOCACK;
	iocp->ioc_count = sizeof(dl_ulong);
	iocp->ioc_error = 0;
	iocp->ioc_rval = 0;

	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC INLINE int
ioc_getname(struct dl *dl, struct iocblk *iocp, mblk_t *mp)
{
	mblk_t *dp;
	caddr_t uaddr = NULL;
	size_t usize;

	ASSERT(iocp->ioc_cmd == LDL_GETNAME);

	if (dl->dlstate == DL_UNATTACHED)
		return ioc_nak(dl, mp);

	ASSERT(dl->ndev != NULL);
	ASSERT(dl->ndev->dev != NULL);

	usize = strlen(dl->ndev->dev->name) + 1;
	if ((dp = allocb(usize, BPRI_HI)) == NULL)
		return RETRY;
	memcpy(dp->b_rptr, dl->ndev->dev->name, usize);
	dp->b_wptr += usize;

#ifdef WITH_32BIT_CONVERSION
	if (iocp->ioc_flag == IOC_ILP32) {
		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t) (unsigned long) (uint32_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		}
	} else
#endif
	{
		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		}
	}

	if (mp->b_cont != NULL)
		freemsg(unlinkb(mp));
	linkb(mp, dp);

	if (iocp->ioc_count == TRANSPARENT) {
		struct copyreq *cq = (typeof(cq)) iocp;

		mp->b_datap->db_type = M_COPYOUT;
		cq->cq_addr = uaddr;
		cq->cq_size = usize;
	} else {

		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = usize;
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
	}

	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC INLINE int
ioc_getgstats(struct dl *dl, struct iocblk *iocp, mblk_t *mp)
{
	mblk_t *dp;
	caddr_t uaddr = NULL;
	size_t usize;

	ASSERT(iocp->ioc_cmd == LDL_GETGSTATS);

#ifdef WITH_32BIT_CONVERSION
	if (iocp->ioc_flag == IOC_ILP32) {
		struct ldl_gstats_ioctl32 *out;

		if ((dp = allocb(sizeof(struct ldl_gstats_ioctl32), BPRI_HI)) == NULL)
			return RETRY;

		out = (typeof(out)) dp->b_rptr;
		usize = sizeof(*out);
		dp->b_wptr += usize;

		out->attach_req_cnt = (int32_t) atomic_read(&ldl_gstats.attach_req_cnt);
		out->detach_req_cnt = (int32_t) atomic_read(&ldl_gstats.detach_req_cnt);
		out->bind_req_cnt = (int32_t) atomic_read(&ldl_gstats.bind_req_cnt);
		out->unbind_req_cnt = (int32_t) atomic_read(&ldl_gstats.unbind_req_cnt);
		out->subs_bind_req_cnt = (int32_t) atomic_read(&ldl_gstats.subs_bind_req_cnt);
		out->subs_unbind_req_cnt = (int32_t) atomic_read(&ldl_gstats.subs_unbind_req_cnt);
		out->udqos_req_cnt = (int32_t) atomic_read(&ldl_gstats.udqos_req_cnt);
		out->ok_ack_cnt = (int32_t) atomic_read(&ldl_gstats.ok_ack_cnt);
		out->error_ack_cnt = (int32_t) atomic_read(&ldl_gstats.error_ack_cnt);
		out->unitdata_req_cnt = (int32_t) atomic_read(&ldl_gstats.unitdata_req_cnt);
		out->unitdata_req_q_cnt = (int32_t) atomic_read(&ldl_gstats.unitdata_req_q_cnt);
		out->unitdata_ind_cnt = (int32_t) atomic_read(&ldl_gstats.unitdata_ind_cnt);
		out->unitdata_q_cnt = (int32_t) atomic_read(&ldl_gstats.unitdata_q_cnt);
		out->unitdata_drp_cnt = (int32_t) atomic_read(&ldl_gstats.unitdata_drp_cnt);
		out->uderror_ind_cnt = (int32_t) atomic_read(&ldl_gstats.uderror_ind_cnt);
		out->ioctl_cnt = (int32_t) atomic_read(&ldl_gstats.ioctl_cnt);
		out->net_rx_cnt = (int32_t) atomic_read(&ldl_gstats.net_rx_cnt);
		out->net_rx_drp_cnt = (int32_t) atomic_read(&ldl_gstats.net_rx_drp_cnt);
		out->net_tx_cnt = (int32_t) atomic_read(&ldl_gstats.net_tx_cnt);
		out->net_tx_fail_cnt = (int32_t) atomic_read(&ldl_gstats.net_tx_fail_cnt);

		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t) (unsigned long) (uint32_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		}
	} else
#endif
	{
		struct ldl_gstats_ioctl *out;

		if ((dp = allocb(sizeof(struct ldl_gstats_ioctl), BPRI_HI)) == NULL)
			return RETRY;

		out = (typeof(out)) dp->b_rptr;
		usize = sizeof(*out);
		dp->b_wptr += usize;

		out->attach_req_cnt = (long) atomic_read(&ldl_gstats.attach_req_cnt);
		out->detach_req_cnt = (long) atomic_read(&ldl_gstats.detach_req_cnt);
		out->bind_req_cnt = (long) atomic_read(&ldl_gstats.bind_req_cnt);
		out->unbind_req_cnt = (long) atomic_read(&ldl_gstats.unbind_req_cnt);
		out->subs_bind_req_cnt = (long) atomic_read(&ldl_gstats.subs_bind_req_cnt);
		out->subs_unbind_req_cnt = (long) atomic_read(&ldl_gstats.subs_unbind_req_cnt);
		out->udqos_req_cnt = (long) atomic_read(&ldl_gstats.udqos_req_cnt);
		out->ok_ack_cnt = (long) atomic_read(&ldl_gstats.ok_ack_cnt);
		out->error_ack_cnt = (long) atomic_read(&ldl_gstats.error_ack_cnt);
		out->unitdata_req_cnt = (long) atomic_read(&ldl_gstats.unitdata_req_cnt);
		out->unitdata_req_q_cnt = (long) atomic_read(&ldl_gstats.unitdata_req_q_cnt);
		out->unitdata_ind_cnt = (long) atomic_read(&ldl_gstats.unitdata_ind_cnt);
		out->unitdata_q_cnt = (long) atomic_read(&ldl_gstats.unitdata_q_cnt);
		out->unitdata_drp_cnt = (long) atomic_read(&ldl_gstats.unitdata_drp_cnt);
		out->uderror_ind_cnt = (long) atomic_read(&ldl_gstats.uderror_ind_cnt);
		out->ioctl_cnt = (long) atomic_read(&ldl_gstats.ioctl_cnt);
		out->net_rx_cnt = (long) atomic_read(&ldl_gstats.net_rx_cnt);
		out->net_rx_drp_cnt = (long) atomic_read(&ldl_gstats.net_rx_drp_cnt);
		out->net_tx_cnt = (long) atomic_read(&ldl_gstats.net_tx_cnt);
		out->net_tx_fail_cnt = (long) atomic_read(&ldl_gstats.net_tx_fail_cnt);

		if (iocp->ioc_count == TRANSPARENT) {
			uaddr = (caddr_t)
			    *(unsigned long *) mp->b_cont->b_rptr;
		}
	}

	if (mp->b_cont != NULL)
		freemsg(unlinkb(mp));
	linkb(mp, dp);

	if (iocp->ioc_count != TRANSPARENT) {

		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = usize;
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
	} else {
		struct copyreq *cq = (typeof(cq)) iocp;

		mp->b_datap->db_type = M_COPYOUT;
		cq->cq_addr = uaddr;
		cq->cq_size = usize;
	}
	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC INLINE int
ioc_set_debug_mask(struct dl *dl, struct iocblk *iocp, mblk_t *mp)
{
	ASSERT(iocp->ioc_cmd == LDL_SETDEBUG);

	if (mp->b_cont == NULL || mp->b_cont->b_datap->db_type != M_DATA)
		return ioc_nak(dl, mp);
#ifdef WITH_32BIT_CONVERSION
	if (iocp->ioc_flag == IOC_ILP32) {
		if (iocp->ioc_count == TRANSPARENT) {
			ldl_debug_mask = *(unsigned long *) mp->b_cont->b_rptr;
		} else {
			if (iocp->ioc_count != sizeof(uint32_t))
				return ioc_nak(dl, mp);
			ldl_debug_mask = *(uint32_t *) mp->b_cont->b_rptr;
		}
	} else
#endif
	{
		if (iocp->ioc_count == TRANSPARENT) {
			ldl_debug_mask = *(unsigned long *) mp->b_cont->b_rptr;
		} else {
			if (iocp->ioc_count != sizeof(unsigned long))
				return ioc_nak(dl, mp);
			ldl_debug_mask = *(unsigned long *) mp->b_cont->b_rptr;
		}
	}

	mp->b_datap->db_type = M_IOCACK;
	iocp->ioc_count = 0;
	iocp->ioc_error = 0;
	iocp->ioc_rval = 0;

	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC int streamscall
ldl_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
	dev_t i;

	spin_lock(&first_open_lock);

	if (sflag == CLONEOPEN) {
		for (i = 1; i < LDL_N_MINOR; i++)
			if (dl_dl[i].rq == NULL)
				break;
		if (i == LDL_N_MINOR) {
			spin_unlock(&first_open_lock);
			return ENXIO;
		}
	} else {
		if ((i = getminor(*devp)) >= LDL_N_MINOR) {
			spin_unlock(&first_open_lock);
			return EBUSY;
		}
	}
	*devp = makedevice(getmajor(*devp), i);

	if (q->q_ptr != NULL) {
		spin_unlock(&first_open_lock);
		return 0;
	}

	ASSERT(dl_dl[i].magic == 0);
	dl_dl[i].magic = DL_MAGIC;
	dl_dl[i].rq = q;
	dl_dl[i].lost_rcv = dl_dl[i].lost_send = 0;
	dl_dl[i].bufwait = 0;
	dl_dl[i].dlstate = DL_UNATTACHED;
	dl_dl[i].priority = 50;
	dl_dl[i].subs = NULL;
	dl_dl[i].flags = LDLFLAG_DEFAULT;
	q->q_ptr = WR(q)->q_ptr = &dl_dl[i];

	dl_dl[i].next_open = first_open;
	first_open = &dl_dl[i];

	if (dl_dl[i].next_open == NULL) {
		spin_unlock(&first_open_lock);
		if (notifier_register() < 0)
			printk("ldl: ldl_open: Unable to add notifier\n");
	} else
		spin_unlock(&first_open_lock);

	qprocson(q);
	return 0;
}

STATIC int streamscall
ldl_close(queue_t *q, int flag, cred_t *crp)
{
	struct dl **dlp, *dl = (struct dl *) q->q_ptr;
	pl_t psw;

	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(dl->rq == q);

	SPLSTR(psw);

	if (dl->bufwait) {
		unbufcall(dl->bufwait);
		dl->bufwait = 0;
	}

	if (dl->dlstate == DL_IDLE) {
		sap_destroy_all(dl);
		dl->dlstate = DL_UNBOUND;
	}
	if (dl->dlstate != DL_UNATTACHED) {
		ndev_release(dl);
		dl->addr_len = 0;
		dl->ndev = NULL;
		dl->dlstate = DL_UNATTACHED;
	}

	SPLX(psw);

	spin_lock(&first_open_lock);
	dl->rq = q->q_ptr = WR(q)->q_ptr = NULL;
	dl->magic = 0;

	for (dlp = &first_open; *dlp; dlp = &(*dlp)->next_open)
		if (*dlp == dl)
			break;

	if (*dlp != NULL)
		*dlp = dl->next_open;
	else
		printk("ldl: ldl_close: Endpoint not on open list\n");

	if (first_open == NULL) {
		spin_unlock(&first_open_lock);
		if (notifier_unregister() < 0)
			printk("ldl: ldl_close: Unable to remove notifier\n");
	} else
		spin_unlock(&first_open_lock);

	qprocsoff(q);
	return 0;
}

STATIC int
write_service_raw(struct dl *dl, mblk_t *mp)
{
	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(!dl->bufwait);

	ASSERT(mp != NULL);
	ASSERT(mp->b_datap != NULL);
	ASSERT(mp->b_rptr != NULL);

	ASSERT(mp->b_datap->db_type == M_DATA);

	if ((dl->flags & LDLFLAG_RAW) != 0) {
		if (ldl_debug_mask & LDL_DEBUG_UDREQ)
			ldl_mp_data_dump("ldl_unitdata_req", mp,
					 ldl_debug_mask & LDL_DEBUG_ALLDATA);
		ginc(unitdata_req_cnt);
		return tx_func_raw(dl, mp);
	}

	printk("dl: Unexpected raw M_DATA message.\n");
	freemsg(mp);

	return DONE;
}

STATIC int
write_service(struct dl *dl, mblk_t *mp)
{
	dl_ulong primitive;

	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(!dl->bufwait);

	ASSERT(mp != NULL);
	ASSERT(mp->b_datap != NULL);
	ASSERT(mp->b_rptr != NULL);

	ASSERT(mp->b_datap->db_type == M_PROTO || mp->b_datap->db_type == M_PCPROTO);

	primitive = ((union DL_primitives *) mp->b_rptr)->dl_primitive;

	if (primitive == DL_UNITDATA_REQ)
		return tx_func_proto(dl, mp);

	switch (primitive) {
	case DL_INFO_REQ:
		return ws_info(dl, mp);
	case DL_PHYS_ADDR_REQ:
		return ws_phys_addr(dl, mp);
	case DL_ATTACH_REQ:
		return ws_attach(dl, mp);
	case DL_DETACH_REQ:
		return ws_detach(dl, mp);
	case DL_BIND_REQ:
		return ws_bind(dl, mp);
	case DL_UNBIND_REQ:
		return ws_unbind(dl, mp);
	case DL_SUBS_BIND_REQ:
		return ws_subs_bind(dl, mp);
	case DL_SUBS_UNBIND_REQ:
		return ws_subs_unbind(dl, mp);
	case DL_UDQOS_REQ:
		return ws_udqos(dl, mp);
	case DL_PROMISCON_REQ:
		return ws_promiscon(dl, mp);
	case DL_PROMISCOFF_REQ:
		return ws_promiscoff(dl, mp);

	case DL_ENABMULTI_REQ:
		return ws_enabmulti(dl, mp);
	case DL_DISABMULTI_REQ:
		return ws_disabmulti(dl, mp);
	case DL_CONNECT_REQ:
	case DL_CONNECT_RES:
	case DL_TOKEN_REQ:
	case DL_DISCONNECT_REQ:
	case DL_RESET_REQ:
	case DL_RESET_RES:
	case DL_DATA_ACK_REQ:
	case DL_REPLY_REQ:
	case DL_REPLY_UPDATE_REQ:
	case DL_XID_REQ:
	case DL_TEST_REQ:
	case DL_SET_PHYS_ADDR_REQ:
	case DL_GET_STATISTICS_REQ:
		return ws_error(dl, mp, primitive, DL_NOTSUPPORTED);
	default:
		return ws_error(dl, mp, primitive, DL_BADPRIM);
	}
}

STATIC int
do_ioctl(struct dl *dl, mblk_t *mp)
{
	struct iocblk *iocp;

	ginc(ioctl_cnt);
	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(mp != NULL);
	ASSERT(mp->b_datap->db_type == M_IOCTL);

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

	switch (iocp->ioc_cmd) {
	case LDL_SETFLAGS:
		return ioc_setflags(dl, iocp, mp);
	case LDL_FINDPPA:
		return ioc_findppa(dl, iocp, mp);
	case LDL_GETNAME:
		return ioc_getname(dl, iocp, mp);
	case LDL_GETGSTATS:
		return ioc_getgstats(dl, iocp, mp);
	case LDL_SETDEBUG:
		return ioc_set_debug_mask(dl, iocp, mp);
	default:
		return ioc_nak(dl, mp);
	}
}

STATIC int
do_iocdata(struct dl *dl, mblk_t *mp)
{
	struct iocblk *iocp = (struct iocblk *) mp->b_rptr;
	struct copyresp *cp = (struct copyresp *) mp->b_rptr;

	if (cp->cp_rval != (caddr_t) 0) {
		freemsg(mp);
		return DONE;
	}
	switch (iocp->ioc_cmd) {
	case LDL_SETFLAGS:
#ifdef WITH_32BIT_CONVERSION
		if (cp->cp_flag == IOC_ILP32) {
			struct ldl_flags_ioctl32 *flg;
			psw_t psw;

			flg = (typeof(flg)) mp->b_cont->b_rptr;
			flg->mask &= ~LDLFLAG_PRIVATE;
			SPLSTR(psw);
			dl->flags = (dl->flags & ~flg->mask) | (flg->flags & flg->mask);
			SPLX(psw);
			flg->flags = dl->flags;
		} else
#endif
		{
			struct ldl_flags_ioctl *flg;
			psw_t psw;

			flg = (typeof(flg)) mp->b_cont->b_rptr;
			flg->mask &= ~LDLFLAG_PRIVATE;
			SPLSTR(psw);
			dl->flags = (dl->flags & ~flg->mask) | (flg->flags & flg->mask);
			SPLX(psw);
			flg->flags = dl->flags;
		}
		break;
	case LDL_FINDPPA:
		return ioc_nak(dl, mp);
	case LDL_GETNAME:
		break;
	case LDL_GETGSTATS:
		break;
	case LDL_SETDEBUG:
		return ioc_nak(dl, mp);
	default:
		return ioc_nak(dl, mp);
	}

	mp->b_datap->db_type = M_IOCACK;
	iocp->ioc_count = 0;
	iocp->ioc_error = 0;
	iocp->ioc_rval = 0;
	qreply(WR(dl->rq), mp);
	return DONE;
}

STATIC streamscall int
ldl_wput(queue_t *q, mblk_t *mp)
{
	unsigned char msg_type = mp->b_datap->db_type;
	dl_ulong primitive;

	if (msg_type == M_PROTO) {
		primitive = ((union DL_primitives *) mp->b_rptr)->dl_primitive;
		if (primitive == DL_UNITDATA_REQ && q->q_count != 0) {
			if (!putq(q, mp))
				freemsg(mp);
			ginc(unitdata_req_q_cnt);
			return (0);
		}
		if (write_service((struct dl *) q->q_ptr, mp) == DONE)
			return (0);
	} else {
		switch (msg_type) {
		case M_PCPROTO:
			if (write_service((struct dl *) q->q_ptr, mp) == DONE)
				return (0);
			break;
		case M_DATA:
			if (q->q_count != 0) {
				if (!putq(q, mp))
					freemsg(mp);
				return (0);
			}
			if (write_service_raw((struct dl *) q->q_ptr, mp) == DONE)
				return (0);
			break;
		case M_FLUSH:
			if (mp->b_rptr[0] & FLUSHW) {
				if (mp->b_rptr[0] & FLUSHBAND)
					flushband(q, mp->b_rptr[1], FLUSHDATA);
				else
					flushq(q, FLUSHDATA);
				mp->b_rptr[0] &= ~FLUSHW;
			}
			if (mp->b_rptr[0] & FLUSHR) {
				if (mp->b_rptr[0] & FLUSHBAND)
					flushband(RD(q), mp->b_rptr[1], FLUSHDATA);
				else
					flushq(RD(q), FLUSHDATA);
				qreply(q, mp);
			} else
				freemsg(mp);
			return (0);
		case M_IOCTL:
			if (do_ioctl((struct dl *) q->q_ptr, mp) == DONE)
				return (0);
			break;
		case M_IOCDATA:
			if (do_iocdata((struct dl *) q->q_ptr, mp) == DONE)
				return (0);
			break;
		default:
			freemsg(mp);
			return (0);
		}
	}
	if (!putq(q, mp))
		freemsg(mp);
	return (0);
}

STATIC streamscall int
ldl_wsrv(queue_t *q)
{
	mblk_t *mp;
	struct dl *dl = (struct dl *) q->q_ptr;

	ASSERT(q != NULL);
	ASSERT(dl != NULL);
	ASSERT(dl->magic == DL_MAGIC);
	ASSERT(!dl->bufwait);

	while ((mp = getq(q)) != NULL) {
		if (mp->b_datap->db_type != M_DATA) {
			ASSERT(mp->b_datap->db_type == M_PROTO
			       || mp->b_datap->db_type == M_PCPROTO);

			if (write_service(dl, mp) == RETRY) {
				if (!putbq(q, mp))
					freemsg(mp);
				return (0);
			}
		} else if (write_service_raw(dl, mp) == RETRY) {
			if (!putbq(q, mp))
				freemsg(mp);
			return (0);
		}
	}
	return (0);
}

STATIC streamscall int
ldl_rsrv(queue_t *q)
{
	mblk_t *mp;

	while (canputnext(q)) {
		if ((mp = getq(q)) == NULL)
			break;

		if (ldl_debug_mask & LDL_DEBUG_UDIND)
			ldl_mp_data_dump("ldl_unitdata_ind", mp,
					 ldl_debug_mask & LDL_DEBUG_ALLDATA);
		putnext(q, mp);
		ginc(unitdata_ind_cnt);
	}
	return (0);
}

#ifdef LINUX
unsigned short modid = DRV_ID;

#ifndef module_param
MODULE_PARM(modid, "h");
#else
module_param(modid, ushort, 0444);
#endif
MODULE_PARM_DESC(modid, "Module ID number for LDL driver (0 for allocation).");

major_t major = CMAJOR_0;

#ifndef module_param
MODULE_PARM(major, "h");
#else
module_param(major, uint, 0444);
#endif
MODULE_PARM_DESC(major, "Major device number for LDL driver (0 for allocation).");
#endif

#ifdef LFS
STATIC struct cdevsw ldl_cdev = {
	.d_name = DRV_NAME,
	.d_str = &ldl_info,
	.d_flag = D_MTPERQ,
	.d_fop = NULL,
	.d_mode = S_IFCHR,
	.d_kmod = THIS_MODULE,
};

struct ldl_ioctl {
	unsigned int cmd;
	void *handle;
};

static struct ldl_ioctl ldl_map[] = {
	{.cmd = LDL_GETLSTATS,}
	, {.cmd = LDL_GETGSTATS,}
	, {.cmd = LDL_SETDEBUG,}
	, {.cmd = LDL_SETFLAGS,}
	, {.cmd = LDL_FINDPPA,}
	, {.cmd = LDL_GETNAME,}
	, {.cmd = 0,}
};

static void
ldl_unregister_ioctl32(void)
{
	struct ldl_ioctl *i;

	for (i = ldl_map; i->cmd != 0; i++)
		if (i->handle != NULL)
			unregister_ioctl32(i->handle);
}

static int
ldl_register_ioctl32(void)
{
	struct ldl_ioctl *i;

	for (i = ldl_map; i->cmd != 0; i++) {
		if ((i->handle = register_ioctl32(i->cmd)) == NULL) {
			ldl_unregister_ioctl32();
			return (-ENOMEM);
		}
	}
	return (0);
}

int __init
ldl_init(void)
{
	int err;

	cmn_err(CE_NOTE, DRV_BANNER);
	if ((err = ldl_register_ioctl32()) < 0)
		return (err);
	if ((err = register_strdev(&ldl_cdev, major)) < 0) {
		cmn_err(CE_WARN, "%s: Cannot register major %d, err = %d\n", DRV_NAME, (int)major, -err);
		ldl_unregister_ioctl32();
		return (err);
	}
	if (err > 0)
		major = err;
	return (0);
}

void __exit
ldl_exit(void)
{
	unregister_strdev(&ldl_cdev, major);
	ldl_unregister_ioctl32();
}

#ifdef CONFIG_STREAMS_LDL_MODULE
module_init(ldl_init);
module_exit(ldl_exit);
#endif

#else
#ifdef LIS

STATIC int ldl_initialized = 0;
STATIC void
ldl_init(void)
{
	int err;

	if (ldl_initialized != 0)
		return;
	cmn_err(CE_NOTE, DRV_BANNER);
	if ((err = lis_register_strdev(major, &ldl_info, UNITS, DRV_NAME)) < 0) {
		cmn_err(CE_WARN, "%s: Cannot register major %d\n", DRV_NAME, major);
		ldl_initialized = err;
		return;
	}
	ldl_initialized = 1;
	if (major == 0 && err > 0) {
		major = err;
		ldl_initialized = 2;
	}
	return;
}
STATIC void
ldl_terminate(void)
{
	int err;

	if (ldl_initialized <= 0)
		return;
	if (major) {
		if ((err = lis_unregister_strdev(major)) < 0)
			cmn_err(CE_PANIC, "%s: Cannot unregister major %d\n", DRV_NAME, major);
		major = 0;
	}
	ldl_initialized = 0;
	return;
}

int
init_module(void)
{
	(void) major;
	ldl_init();
	if (ldl_initialized < 0)
		return ldl_initialized;
	return (0);
}

void
cleanup_module(void)
{
	return ldl_terminate();
}

#endif
#endif

OpenSS7
SS7 for the
Common Man
Home TopIndex FirstPrev Next LastMore Download Info FAQ Mail  Home -> Resources -> Browse Source -> /code/strxns/src/drivers/ldl.c
Last modified: Sat, 16 Aug 2008 01:55:14 GMT
© Copyright 1997-2007 OpenSS7 Corporation All Rights Reserved.