ftp.nice.ch/pub/next/unix/network/system/venet.1-1.1.s.tar.gz#/venet1/venet1.c

This is venet1.c in view mode; [Download] [Up]

/*
 * LKS for EtherTalk Phase 1 (and AARP)
 * Eric P. Scott, San Francisco State University, February 1993
 * Copyright 1993 by Eric P. Scott.  All rights reserved.
 *
 * Portions based on sample code Copyright 1990 by NeXT Computer, Inc.
 * Portions derived from code developed by the University of California
 * at Berkeley.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notices, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notices, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. All advertising materials mentioning features or use of this
 *    software, must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the author, the Universities, nor the names of
 *    additional contributors may be used to endorse or promote products
 *    derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR, REGENTS, TRUSTEES, OR ADDITIONAL
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * (Amended notice Copyright (c) 1989 The Regents of the University of
 * California.)
 *
 *
 * For uniprocessor systems only!
 *
 * To compile for NeXT Software Release 2.0/2.1/2.2/2.2a:
 * cc -c -DKERNEL -DKERNEL_FEATURES -DMACH -O venet1.c
 *
 * To compile for NeXT Software Release 3.0:
 * cc -c -DKERNEL -DMACH_USER_API -DMACH -O2 -Wall venet1.c
 *
 * Optional features:
 *   -DFASTER_QUEUE => smaller code, faster (but incompatible) input queue
 *   -DSEGREGATE => support for etI[AE]? minor devices
 *
 *
 * HISTORY
 *	1.0	 3/ 8/93  EPS	Initial nonrelease.
 *	1.1	 3/13/93  EPS	Added SEGREGATE option.
 *
 *	@(#)venet1.c	1.1 (SFSU) 3/13/93
 */
static char sccsid[]={	/* for "what" command */
	'@', '(', '#', ')',
	'v', 'e', 'n', 'e', 't', '1', '.', 'c', '\t',
	'1', '.', '1',
#ifdef FASTER_QUEUE
	'-', 'F', 'Q',
#endif
#ifdef SEGREGATE
	'-', 'E', 'A',
#endif
	' ', '(', 'S', 'F', 'S', 'U', ')', ' ',
	'3', '/', '1', '3', '/', '9', '3', '\0'
};

/* Maximum number of Ethernet interfaces (1..64) */
#ifndef ET1_MAXMIN
#define ET1_MAXMIN 4
#endif

#include <kernserv/kern_server_types.h>
#include <kernserv/kern_server_reply.h>

#ifdef MACH_USER_API
#include <bsd/dev/ldd.h>
#include <bsd/sys/conf.h>
#include <bsd/sys/systm.h>
#include <bsd/sys/file.h>
/* <bsd/sys/user.h> erroneously imports <kern/lock.h>, generates a warning */
#include <bsd/sys/proc.h>
#include <bsd/sys/socket.h>
#include <bsd/netinet/in.h>
#ifdef m68k
#include <kernserv/m68k/spl.h>
#else
#include <bsd/machine/spl.h>
#endif
#include <bsd/sys/mbuf.h>
#include <bsd/net/if.h>
#include <bsd/netinet/if_ether.h>
extern int selthreadcache(void **);
extern void selthreadclear(void **);
extern int selwakeup(void *, int);
#else
#include <nextdev/ldd.h>
extern int strcmp(const char *, const char *);
#include <sys/conf.h>
#include <sys/systm.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <machine/spl.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <netinet/if_ether.h>
extern int selwakeup(thread_t, int);
#endif
extern void gsignal(int, int);
extern int uiomove(caddr_t, int, enum uio_rw, struct uio *);
extern void m_freem(struct mbuf *);
extern void mclput(struct mbuf *);
#ifdef FASTER_QUEUE
#undef IF_DEQUEUEIF
#undef IF_ADJ
#endif
#define	ETHERTYPE_ET1	0x809b		/* EtherTalk Phase 1 protocol */
#define ETHERTYPE_AARP	0x80f3		/* AppleTalk AARP */
#define ETYPEOFFSET 12
const char IFTYPE_ET1[]="EtherTalk Phase 1 Protocol Access";
#ifndef NULL
#define NULL ((void *)0)
#endif

#ifndef PRINET
#define PRINET 26	/* interruptible */
#endif

typedef struct {
    struct ether_addr vp_enaddr;	/* real device's Ethernet address */
#define ET1_ISOPEN 1
#define ET1_WAKE 2
#define ET1_RCOLL 4
#define ET1_WCOLL 8
#define ET1_NBIO 16
#define ET1_ASYNC 32
    short vp_state;		/* per-unit state */
    netif_t vp_rifp;		/* real device's netif */
    struct ifqueue vp_intrq;	/* input queue */
    off_t vp_nread;		/* number of bytes queued */
#ifdef MACH_USER_API
    void *vp_selr;		/* thread selecting for read */
    void *vp_selw;		/* thread selecting for write */
#else
    thread_t vp_selr;		/* thread selecting for read */
    thread_t vp_selw;		/* thread selecting for write */
#endif
    short vp_pgrp;		/* process|group for SIGIO */
#ifdef SEGREGATE
/*
 * This option extends the driver to support two additional minor
 * devices per real interface.  The "base" device operates as it always
 * has, returning both EtherTalk Phase 1 and AARP datagrams in sequence.
 * The etIE? (?+64) and etIA? (?+128) minors allow access to the two
 * types separately.  etI? and etIE? share data since they can't be
 * opened simultaneously.  Also, the SIOCGIFADDR and SIOCGIFBRDADDR
 * ioctls are extended to return ether_type when applied to a single-
 * type minor.
 *
 * [Unfortunately, doing this made the code truly horrid, not to mention
 *  difficult to understand (but silenced certain whiners, whose names
 *  I don't care to mention).  So, if you're looking for good sample
 *  code, rip out everything where SEGREGATE is true...]
 *
 */
#define ET1_EOPEN 64
#define ET1_AOPEN 128
#define ET1_AWAKE 256
#define ET1_ARCOLL 512
#define ET1_AWCOLL 1024
#define ET1_ANBIO 2048
#define ET1_AASYNC 4096
    struct ifqueue vp_aintrq;	/* alternate input queue */
    off_t vp_anread;		/* number of bytes queued */
#ifdef MACH_USER_API
    void *vp_aselr;		/* thread selecting for read */
    void *vp_aselw;		/* thread selecting for write */
#else
    thread_t vp_aselr;		/* thread selecting for read */
    thread_t vp_aselw;		/* thread selecting for write */
#endif
    short vp_apgrp;		/* process|group for SIGIO */
#endif /* SEGREGATE */
} venet1_private_t;

static inline venet1_private_t *VENET1_PRIVATE(netif_t ifp)
{
    return((venet1_private_t *)if_private(ifp));
}

static inline struct ether_addr *VENET1_ENADDRP(netif_t ifp)
{
    return(&((venet1_private_t *)if_private(ifp))->vp_enaddr);
}

static inline netif_t VENET1_RIF(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_rifp);
}

static inline short VENET1_STATE(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_state);
}

static inline void VENET1_STATE_BIS(netif_t ifp, short bits)
{
    ((venet1_private_t *)if_private(ifp))->vp_state|=bits;
}

static inline void VENET1_STATE_BIC(netif_t ifp, short bits)
{
    ((venet1_private_t *)if_private(ifp))->vp_state&=~bits;
}

static inline struct ifqueue *VENET1_INTRQP(netif_t ifp)
{
    return(&((venet1_private_t *)if_private(ifp))->vp_intrq);
}

static inline off_t VENET1_NREAD(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_nread);
}

static inline short VENET1_PGRP(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_pgrp);
}

static inline void VENET1_PGRP_SET(netif_t ifp, short pgrp)
{
    ((venet1_private_t *)if_private(ifp))->vp_pgrp=pgrp;
}
#ifdef SEGREGATE

static inline struct ifqueue *VENET1_AINTRQP(netif_t ifp)
{
    return(&((venet1_private_t *)if_private(ifp))->vp_aintrq);
}

static inline off_t VENET1_ANREAD(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_anread);
}

static inline short VENET1_APGRP(netif_t ifp)
{
    return(((venet1_private_t *)if_private(ifp))->vp_apgrp);
}

static inline void VENET1_APGRP_SET(netif_t ifp, short pgrp)
{
    ((venet1_private_t *)if_private(ifp))->vp_apgrp=pgrp;
}
#endif /* SEGREGATE */

extern int nulldev(dev_t, ...), nodev(dev_t, ...), seltrue(dev_t, int);

struct {
    kern_server_t kern_server;
    int et1maj;			/* our major device number */
    struct cdevsw saved_cdevsw;	/* saved cdevsw entry */
    netif_t et1if[ET1_MAXMIN];	/* pointers to "our" netifs */
    int et1nif;			/* number of ifs/minor devices */
    int et1unloading;		/* set when we're going away */
} instance;

int et1qmaxlen=IFQ_MAXLEN;	/* maximum incoming datagrams we'll queue */

#ifndef NOTDEF
int et1verbose=1;
#else
extern int verbose;
#define et1verbose verbose
#endif

int et1open(dev_t, int, dev_t *), et1close(dev_t, int),
    et1read(dev_t, struct uio *), et1write(dev_t, struct uio *),
    et1ioctl(dev_t, int, caddr_t, int), et1select(dev_t, int);

struct cdevsw my_cdevsw={	/*19*/
	(int (*)())et1open,	(int (*)())et1close,	(int (*)())et1read,
	(int (*)())et1write,	(int (*)())et1ioctl,	(int (*)())nodev,
	(int (*)())nulldev,	(int (*)())et1select,	(int (*)())nodev,
#ifdef NeXT
	(int (*)())nodev,	(int (*)())nodev
#endif
};


/* Call from Load_Commands.sect with major device number */
void
venet1_init(int idx)
{
    void venet1_config(void);
    extern int nchrdev;

    if (idx<=0||idx>=nchrdev||cdevsw[idx].d_open!=(int (*)())nodev||
	cdevsw[idx].d_reset!=(int (*)())nodev)
	kern_serv_panic(kern_serv_bootstrap_port(&instance.kern_server),
	"venet1_init: requested major device number unavailable");
    else {
	instance.et1unloading=0;
	venet1_config();
	instance.et1maj=idx;
	instance.saved_cdevsw=cdevsw[idx];
	cdevsw[idx]=my_cdevsw;
    }
}

/* Call from Unload_Commands.sect */
/*ARGSUSED*/
void
venet1_signoff(int idx)
{
    void venet1_detach(void);
    register int i;

    instance.et1unloading=1;
    i=instance.et1maj;
    cdevsw[i]=instance.saved_cdevsw;
    for (i=0;i<instance.et1nif;i++) {
	register netif_t ni;
	int s;

	ni=instance.et1if[i];
	if (ni&&(VENET1_STATE(ni)&ET1_ISOPEN)) {
	    /* we are in deep, deep yogurt */
	    struct ifqueue *ifq;

	    printf("ET1 protocol access was open for interface %s%u!\n",
		if_name(ni), if_unit(ni));
	    s=splimp();
	    if (VENET1_STATE(ni)&ET1_WAKE) {
		VENET1_STATE_BIC(ni, ET1_WAKE);
		wakeup((caddr_t)VENET1_INTRQP(ni));
	    }
	    if (VENET1_PRIVATE(ni)->vp_selr) {
		(void)selwakeup(VENET1_PRIVATE(ni)->vp_selr,
		    VENET1_STATE(ni)&ET1_RCOLL);
#ifdef MACH_USER_API
		selthreadclear(&VENET1_PRIVATE(ni)->vp_selr);
#else
		VENET1_PRIVATE(ni)->vp_selr=THREAD_NULL;
#endif
		VENET1_STATE_BIC(ni, ET1_RCOLL);
	    }
	    if (VENET1_PRIVATE(ni)->vp_selw) {
		(void)selwakeup(VENET1_PRIVATE(ni)->vp_selw,
		    VENET1_STATE(ni)&ET1_WCOLL);
#ifdef MACH_USER_API
		selthreadclear(&VENET1_PRIVATE(ni)->vp_selw);
#else
		VENET1_PRIVATE(ni)->vp_selw=THREAD_NULL;
#endif
		VENET1_STATE_BIC(ni, ET1_WCOLL);
	    }
#ifdef SEGREGATE
	    if (VENET1_STATE(ni)&ET1_AWAKE) {
		VENET1_STATE_BIC(ni, ET1_AWAKE);
		wakeup((caddr_t)VENET1_AINTRQP(ni));
	    }
	    if (VENET1_PRIVATE(ni)->vp_aselr) {
		(void)selwakeup(VENET1_PRIVATE(ni)->vp_aselr,
		    VENET1_STATE(ni)&ET1_ARCOLL);
#ifdef MACH_USER_API
		selthreadclear(&VENET1_PRIVATE(ni)->vp_aselr);
#else
		VENET1_PRIVATE(ni)->vp_aselr=THREAD_NULL;
#endif
		VENET1_STATE_BIC(ni, ET1_ARCOLL);
	    }
	    if (VENET1_PRIVATE(ni)->vp_aselw) {
		(void)selwakeup(VENET1_PRIVATE(ni)->vp_aselw,
		    VENET1_STATE(ni)&ET1_AWCOLL);
#ifdef MACH_USER_API
		selthreadclear(&VENET1_PRIVATE(ni)->vp_aselw);
#else
		VENET1_PRIVATE(ni)->vp_aselw=THREAD_NULL;
#endif
		VENET1_STATE_BIC(ni, ET1_AWCOLL);
	    }
#endif /* SEGREGATE */
	    /* maybe send SIGIO here? */
	    (void)splx(s);
	    /* sigh, whimper */
	    assert_wait(0, FALSE);
	    thread_set_timeout(hz*4);
	    thread_block();
	    /* 4... 3... 2... 1... */
	    s=splimp();
	    ifq=VENET1_INTRQP(ni);
	    for (;;) {	/* empty input queue */
		register struct mbuf *m;

		IF_DEQUEUE(ifq, m);
		if (!m) break;
#ifndef FASTER_QUEUE
		IF_ADJ(m);
		if (m)
#endif
		    VENET1_PRIVATE(ni)->vp_nread-=(off_t)m->m_len;
		(void)splx(s);
		m_freem(m);
		s=splimp();
	    }
#ifdef SEGREGATE
	    ifq=VENET1_AINTRQP(ni);
	    for (;;) {	/* empty input queue */
		register struct mbuf *m;

		IF_DEQUEUE(ifq, m);
		if (!m) break;
#ifndef FASTER_QUEUE
		IF_ADJ(m);
		if (m)
#endif
		    VENET1_PRIVATE(ni)->vp_anread-=(off_t)m->m_len;
		(void)splx(s);
		m_freem(m);
		s=splimp();
	    }
#endif /* SEGREGATE */
	    (void)splx(s);
	}
    }
    venet1_detach();
}


/* Open pseudo-device.  Fails with EBUSY if someone already has it. */
/*ARGSUSED*/
int
et1open(dev_t dev, int flags, dev_t *newdev)
{
    register netif_t ni;
    int s;

#ifdef SEGREGATE
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif /* SEGREGATE */
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    s=splimp();
#ifdef SEGREGATE
    switch ((dev>>6)&3) {
    case 0:
	if (VENET1_STATE(ni)&ET1_ISOPEN) {
	    (void)splx(s);
	    return(EBUSY);	/* exclusive open */
	}
	VENET1_STATE_BIS(ni, ET1_ISOPEN);
#ifdef NOTDEF
	if (!VENET1_PGRP(ni)) VENET1_PGRP_SET(ni, (u.u_procp)->p_pid);
#endif
	break;
    case 1:
	switch (VENET1_STATE(ni)&(ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN)) {
	case 0:
	case ET1_ISOPEN|ET1_AOPEN:
	    break;
	default:
	    (void)splx(s);
	    return(EBUSY);	/* exclusive open/interlock */
	}
	VENET1_STATE_BIS(ni, ET1_ISOPEN|ET1_EOPEN);
#ifdef NOTDEF
	if (!VENET1_PGRP(ni)) VENET1_PGRP_SET(ni, (u.u_procp)->p_pid);
#endif
	break;
    case 2:
	switch (VENET1_STATE(ni)&(ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN)) {
	case 0:
	case ET1_ISOPEN|ET1_EOPEN:
	    break;
	default:
	    (void)splx(s);
	    return(EBUSY);	/* exclusive open/interlock */
	}
	VENET1_STATE_BIS(ni, ET1_ISOPEN|ET1_AOPEN);
#ifdef NOTDEF
	if (!VENET1_APGRP(ni)) VENET1_APGRP_SET(ni, (u.u_procp)->p_pid);
#endif
	break;
    default:
	/*
	 * I'd like to reserve the unused quarter of minor device space
	 * for [future] etIs? (status) devices: these would allow
	 * multiple opens for access to status ioctls only (no read/
	 * write, would always select true, etc.).  For SNMP agents and
	 * the like.  --EPS
	 */
	(void)splx(s);
	return(ENXIO);	/* not yet implemented */
    }
#else /* SEGREGATE */
    if (VENET1_STATE(ni)&ET1_ISOPEN) {
	(void)splx(s);
	return(EBUSY);	/* exclusive open */
    }
    VENET1_STATE_BIS(ni, ET1_ISOPEN);
#ifdef NOTDEF
    if (!VENET1_PGRP(ni)) VENET1_PGRP_SET(ni, (u.u_procp)->p_pid);
#endif
#endif /* SEGREGATE */
    (void)splx(s);
    return(0);
}

/* Last close on pseudo-device.  Discards any unread datagrams. */
/*ARGSUSED*/
int
et1close(dev_t dev, int flags)
{
    register netif_t ni;
    register struct mbuf *m;
    struct ifqueue *ifq;
    int s;
#ifdef MACH_USER_API
    register void **selp;
#endif

#ifdef SEGREGATE
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif /* SEGREGATE */
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    s=splimp();
#ifdef SEGREGATE
    switch ((dev>>6)&3) {
    case 0:
	VENET1_STATE_BIC(ni, ET1_ISOPEN);
	VENET1_PGRP_SET(ni, 0);
	ifq=VENET1_INTRQP(ni);
	break;
    case 1:
	VENET1_STATE_BIC(ni, (VENET1_STATE(ni)&ET1_AOPEN) ? ET1_EOPEN :
	    ET1_ISOPEN|ET1_EOPEN);
	VENET1_PGRP_SET(ni, 0);
	ifq=VENET1_INTRQP(ni);
	break;
    default:
	VENET1_STATE_BIC(ni, (VENET1_STATE(ni)&ET1_EOPEN) ? ET1_AOPEN :
	    ET1_ISOPEN|ET1_AOPEN);
	VENET1_APGRP_SET(ni, 0);
	ifq=VENET1_AINTRQP(ni);
	break;
    }
#else
    VENET1_STATE_BIC(ni, ET1_ISOPEN);
    VENET1_PGRP_SET(ni, 0);
    ifq=VENET1_INTRQP(ni);
#endif /* SEGREGATE */
    for (;;) {	/* empty input queue */
	IF_DEQUEUE(ifq, m);
	if (!m) break;
#ifndef FASTER_QUEUE
	IF_ADJ(m);
#endif
	(void)splx(s);
	m_freem(m);
	s=splimp();
    }
#ifdef SEGREGATE
    if (dev&128) {
	VENET1_PRIVATE(ni)->vp_anread=0L;
	(void)splx(s);
#ifdef MACH_USER_API
	selp= &VENET1_PRIVATE(ni)->vp_aselr;
	if (*selp) selthreadclear(selp);
	selp= &VENET1_PRIVATE(ni)->vp_aselw;
	if (*selp) selthreadclear(selp);
#else
	VENET1_PRIVATE(ni)->vp_aselr=THREAD_NULL;
	VENET1_PRIVATE(ni)->vp_aselw=THREAD_NULL;
#endif
    }
    else {
#endif /* SEGREGATE */
    VENET1_PRIVATE(ni)->vp_nread=0L;
    (void)splx(s);
#ifdef MACH_USER_API
    selp= &VENET1_PRIVATE(ni)->vp_selr;
    if (*selp) selthreadclear(selp);
    selp= &VENET1_PRIVATE(ni)->vp_selw;
    if (*selp) selthreadclear(selp);
#else
    VENET1_PRIVATE(ni)->vp_selr=THREAD_NULL;
    VENET1_PRIVATE(ni)->vp_selw=THREAD_NULL;
#endif
#ifdef SEGREGATE
    }
#endif /* SEGREGATE */
    return(0);
}

/* Read one datagram (atomic).  If user requests fewer bytes
 * than the datagram offers, some data will be discarded.
 *
 * N.B. this is guaranteed to be called with uio->uio_resid!=0
 */
int
et1read(dev_t dev, register struct uio *uio)
{
    /* no way to peek :-( */
    register netif_t ni;
    register struct mbuf *m;
    struct ifqueue *ifq;
    int s;
    int error;

#ifdef SEGREGATE
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    error=0;
    s=splimp();
#ifdef SEGREGATE
    if (dev&128) {
	ifq=VENET1_AINTRQP(ni);
	for (;;) {
	    IF_DEQUEUE(ifq, m);
	    if (!m) {
		if (VENET1_STATE(ni)&ET1_ANBIO) error=EWOULDBLOCK;
		else {
		    VENET1_STATE_BIS(ni, ET1_AWAKE);
		    sleep((caddr_t)ifq, PRINET);
		    if (instance.et1unloading==0) continue;
		    error=ENXIO;
		}
	    }
	    else {
#ifndef FASTER_QUEUE
		IF_ADJ(m);
		if (m)
#endif
		    VENET1_PRIVATE(ni)->vp_anread-=(off_t)m->m_len;
	    }
	    break;
	}
    }
    else {
#endif /* SEGREGATE */
    ifq=VENET1_INTRQP(ni);
    for (;;) {
	IF_DEQUEUE(ifq, m);
	if (!m) {
	    if (VENET1_STATE(ni)&ET1_NBIO) error=EWOULDBLOCK;
	    else {
		VENET1_STATE_BIS(ni, ET1_WAKE);
		sleep((caddr_t)ifq, PRINET);
		if (instance.et1unloading==0) continue;
		error=ENXIO;
	    }
	}
	else {
#ifndef FASTER_QUEUE
	    IF_ADJ(m);
	    if (m)
#endif
		VENET1_PRIVATE(ni)->vp_nread-=(off_t)m->m_len;
	}
	break;
    }
#ifdef SEGREGATE
    }
#endif /* SEGREGATE */
    (void)splx(s);
    if (m) {
	do {
	    if ((error=uiomove(mtod(m, caddr_t), min(uio->uio_resid, m->m_len),
		UIO_READ, uio))!=0) break;
	    m=m_free(m);
	    if (!m) goto done;
	} while (uio->uio_resid>0);
	m_freem(m);
    }
done:
    return(error);
}

/* Write one datagram (atomic).  Ethernet addresses and datagram
 * type must immediately precede data.  Fails with EINVAL if
 * fewer than 14 bytes provided (of course, you should provide
 * at least ETHERMIN); EMSGSIZE if too long.  Fails with
 * EADDRNOTAVAIL if ether_type is something we don't understand.
 *
 * Nonblocking output is not implemented.
 */
int
et1write(dev_t dev, register struct uio *uio)
{
    register netif_t ni;
    netbuf_t nb;
    struct sockaddr dst;
    int error;

#ifdef SEGREGATE
    register int s;
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif /* SEGREGATE */
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    if (uio->uio_resid<ETHERHDRSIZE) return(EINVAL);
    if (uio->uio_resid>if_mtu(ni)+ETHERHDRSIZE) return(EMSGSIZE);
    if (!(nb=if_getbuf(ni))) return(ENOBUFS);
    error=uiomove((caddr_t)&dst.sa_data, ETHERHDRSIZE, UIO_WRITE, uio);
    if (error==0) {
	unsigned int n, nbmax;

	nbmax=nb_size(nb);
	n=uio->uio_resid;
	if (n>nbmax) error=EMSGSIZE;
	else {
	    (void)nb_shrink_bot(nb, nbmax-n);
	    error=uiomove((caddr_t)nb_map(nb), (int)n, UIO_WRITE, uio);
	    if (error==0) {
		register int s;

		switch (ntohs(((struct ether_header *)&dst.sa_data)->
		    ether_type)) {
#ifdef SEGREGATE
		case ETHERTYPE_AARP:
		    if (dev&64) goto badtype;
		    goto typeok;
		case ETHERTYPE_ET1:
		    if (dev&128) goto badtype;
		typeok:

#else
		case ETHERTYPE_ET1:
		case ETHERTYPE_AARP:
#endif /* SEGREGATE */
		    dst.sa_family=AF_UNSPEC;
		    s=splnet();
		    error=if_output(ni, nb, (void *)&dst);
		    (void)splx(s);
		    /* if_output _nearly_ always frees its netbuf (grr!) */
		    if (error!=ENXIO) return(error);
		    break;
		default:
#ifdef SEGREGATE
		badtype:
#endif /* SEGREGATE */
		    error=EADDRNOTAVAIL;
		    break;
		}
	    }
	}
    }
    nb_free(nb);
    return(error);
}

/* stuff device name and unit in an ifreq a la SIOCGIFCONF */
static void
stuffname(netif_t ni, struct ifreq *ifrp)
{
    register char *o, *e;
    register const char *i;

    o=ifrp->ifr_name; e=o+IFNAMSIZ-2;
    i=if_name(ni);
    if (i) {
	while (o<e&&*i) *o++= *i++;
	*o++=if_unit(ni)+'0';	/* no one has 11 Ethernet interfaces :-) */
    }
    *o='\0';
}

/* The usual device controls and a few surprises.
 * A few things one normally associates with sockets work here,
 * but with slightly different semantics.  In particular,
 * (struct ifreq *)->ifr_name is an output parameter.
 *
 * There should be some way to retrieve statistics (e.g. queue drops).
 */
/*ARGSUSED*/
int
et1ioctl(dev_t dev, int cmd, caddr_t data, int flag)
{
    register netif_t ni;
    int error;
    off_t n;
    int s;

#ifdef SEGREGATE
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif /* SEGREGATE */
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    error=0;
    switch (cmd) {
    case FIONREAD:
	s=splimp();
#ifdef SEGREGATE
	n=(dev&128) ? VENET1_ANREAD(ni) : VENET1_NREAD(ni);
#else
	n=VENET1_NREAD(ni);
#endif /* SEGREGATE */
	(void)splx(s);
	*(off_t *)data=n;	/* <sys/ioctl.h> says int?!? */
	break;
    case FIONBIO:
#ifdef SEGREGATE
	if (dev&128) {
	    if (*(int *)data) VENET1_STATE_BIS(ni, ET1_ANBIO);
	    else VENET1_STATE_BIC(ni, ET1_ANBIO);
	}
	else
#endif /* SEGREGATE */
	if (*(int *)data) VENET1_STATE_BIS(ni, ET1_NBIO);
	else VENET1_STATE_BIC(ni, ET1_NBIO);
	break;
    case FIOASYNC:
#ifdef SEGREGATE
	if (dev&128) {
	    if (*(int *)data) VENET1_STATE_BIS(ni, ET1_AASYNC);
	    else VENET1_STATE_BIC(ni, ET1_AASYNC);
	}
	else
#endif /* SEGREGATE */
	if (*(int *)data) VENET1_STATE_BIS(ni, ET1_ASYNC);
	else VENET1_STATE_BIC(ni, ET1_ASYNC);
	break;
    case TIOCGPGRP:
#ifdef SEGREGATE
	*(int *)data=(dev&128) ? VENET1_APGRP(ni) : VENET1_PGRP(ni);
#else
	*(int *)data=VENET1_PGRP(ni);
#endif /* SEGREGATE */
	break;
    case TIOCSPGRP:
	if (*(int *)data>0&&!pfind(*(int *)data)) return(ESRCH);
	switch (*(int *)data) {		/* EPS' paranoia */
	case -2:	/* mach_init */
	case -1:	/* various daemons */
	case 1:		/* init */
	case 2:		/* mach_init */
	case 3:		/* kern_loader */
	    if (!suser()) return(EPERM);
	    break;
	default:
	    /* I could be more obnoxious and only allow my pid and my pgrp */
	    break;
	}
#ifdef SEGREGATE
	if (dev&128) VENET1_APGRP_SET(ni, (short)*(int *)data);
	else
#endif
	VENET1_PGRP_SET(ni, (short)*(int *)data);
	break;
    case SIOCGIFADDR:
	stuffname(ni, (struct ifreq *)data);
	((struct ifreq *)data)->ifr_addr.sa_family=AF_UNSPEC;
	bcopy((void *)VENET1_ENADDRP(ni)->ether_addr_octet, (void *)
((struct ether_header *)&((struct ifreq *)data)->ifr_addr.sa_data)->
	    ether_shost, sizeof (struct ether_addr));
#ifdef SEGREGATE
	if (dev&64) ((struct ether_header *)
	    &((struct ifreq *)data)->ifr_addr.sa_data)->
	    ether_type=ETHERTYPE_ET1;
	else if (dev&128) ((struct ether_header *)
	    &((struct ifreq *)data)->ifr_addr.sa_data)->
	    ether_type=ETHERTYPE_AARP;
#endif
	break;
    case SIOCGIFFLAGS:
	stuffname(ni, (struct ifreq *)data);
	((struct ifreq *)data)->ifr_flags=if_flags(ni);
	break;
    case SIOCGIFBRDADDR:
	stuffname(ni, (struct ifreq *)data);
	((struct ifreq *)data)->ifr_addr.sa_family=AF_UNSPEC;
	bcopy((void *)etherbroadcastaddr, (void *)
((struct ether_header *)&((struct ifreq *)data)->ifr_broadaddr.sa_data)->
	    ether_dhost, sizeof (struct ether_addr));
#ifdef SEGREGATE
	if (dev&64) ((struct ether_header *)
	    &((struct ifreq *)data)->ifr_addr.sa_data)->
	    ether_type=ETHERTYPE_ET1;
	else if (dev&128) ((struct ether_header *)
	    &((struct ifreq *)data)->ifr_addr.sa_data)->
	    ether_type=ETHERTYPE_AARP;
#endif
	break;
    case SIOCGIFMTU:
	stuffname(ni, (struct ifreq *)data);
	((struct ifreq *)data)->ifr_mtu=if_mtu(ni);
	break;
    default:
	switch (((cmd)>>8)&0xff) {
	case 't':
	    error=ENOTTY;
	    break;
	case 'i':
	case 'r':
	    error=EINVAL;
	    break;
	default:
	    error=if_ioctl(ni, cmd, data);
	    break;
	}
	break;
    }
    return(error);
}

/* Lame select() routine.  Only read does anything useful. */
int
et1select(register dev_t dev, int rw)
{
    register netif_t ni;
    int s, ret;

#ifdef SEGREGATE
    s=dev&0x3f;
    if (s>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[s];
#else
    if (minor(dev)>=instance.et1nif) return(ENXIO);
    ni=instance.et1if[minor(dev)];
#endif
#ifdef NOTDEF
    if (!ni) return(ENXIO);
#endif
    ret=0;
    s=splimp();
    switch (rw) {
    case FREAD:
#ifdef SEGREGATE
	if (dev&128) {
	    if (VENET1_ANREAD(ni)>0L) ret=1;
	    else {
#ifdef MACH_USER_API
		if (selthreadcache(&VENET1_PRIVATE(ni)->vp_aselr))
		    VENET1_STATE_BIS(ni, ET1_ARCOLL);
#else
		register thread_t t=VENET1_PRIVATE(ni)->vp_aselr;
		if (t&&t->wait_event==(int)&selwait)
		    VENET1_STATE_BIS(ni, ET1_ARCOLL);
		else VENET1_PRIVATE(ni)->vp_aselr=current_thread();
#endif
	    }
	}
	else
#endif /* SEGREGATE */
	if (VENET1_NREAD(ni)>0L) ret=1;
	else {
#ifdef MACH_USER_API
	    if (selthreadcache(&VENET1_PRIVATE(ni)->vp_selr))
		VENET1_STATE_BIS(ni, ET1_RCOLL);
#else
	    register thread_t t=VENET1_PRIVATE(ni)->vp_selr;
	    if (t&&t->wait_event==(int)&selwait)
		VENET1_STATE_BIS(ni, ET1_RCOLL);
	    else VENET1_PRIVATE(ni)->vp_selr=current_thread();
#endif
	}
	break;
    case FWRITE:
#ifndef NOTDEF
	ret=1;	/* no support for nonblocking write (yet?) */
#else /* NOTDEF */
	if (!ret) {
#ifdef MACH_USER_API
	    if (selthreadcache(&VENET1_PRIVATE(ni)->vp_selw))
		VENET1_STATE_BIS(ni, ET1_WCOLL);
#else
	    register thread_t t=VENET1_PRIVATE(ni)->vp_selw;
	    if (t&&t->wait_event==(int)&selwait)
		VENET1_STATE_BIS(ni, ET1_WCOLL);
	    else VENET1_PRIVATE(ni)->vp_selw=current_thread();
#endif
	}
#endif /* NOTDEF */
	break;
    }
    (void)splx(s);
    return(ret);
}


/* most of what follows is cribbed from _Writing Loadable Kernel Servers_ */

static int
venet1_output(netif_t ifp, netbuf_t nb, void *addr)
{
    struct sockaddr *dst=(struct sockaddr *)addr;
    struct ether_header eh;
    int error;

    switch (dst->sa_family) {
    case AF_UNSPEC:
	bcopy(dst->sa_data, &eh, sizeof(eh));
	break;
    default:
	nb_free(nb);
	return (EAFNOSUPPORT);
    }
    nb_grow_top(nb, ETHERHDRSIZE);
    nb_write(nb, ETYPEOFFSET, sizeof(eh.ether_type), (void *)&eh.ether_type);
    error=if_output(VENET1_RIF(ifp), nb, (void *)&eh.ether_dhost);
    if (error==0) if_opackets_set(ifp, if_opackets(ifp) + 1);
    else if_oerrors_set(ifp, if_oerrors(ifp) + 1);
    return (error);
}

static int
venet1_control(netif_t ifp, const char *command, void *data)
{
    if (strcmp(command, IFCONTROL_AUTOADDR)==0||
	strcmp(command, IFCONTROL_SETADDR)==0)
	return (EAFNOSUPPORT);
    else {
	/*
	 * Let lower layer handle
	 */
	return (if_control(VENET1_RIF(ifp), command, data));
    }
    return (0);
}

static netbuf_t
venet1_getbuf(netif_t ifp)
{
    netbuf_t nb;

    nb=if_getbuf(VENET1_RIF(ifp));
    if (nb) nb_shrink_top(nb, ETHERHDRSIZE);
    return(nb);
}

/* inet_queue() analog that places incoming datagram on private queue */
/* [based on BSD loopback interface driver] */

/*
 * BEGIN: BSD copyrighted material
 */
/*
 * Copyright (c) 1982, 1986 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 */
static void
et1_queue(netif_t netif, netbuf_t netbuf) {
    register struct mbuf *m0;
    register struct ifqueue *ifq;
    register int s;
    off_t n;

    m0=(struct mbuf *)netbuf;	/* netbufs are really loaned mbufs! */
    ifq=VENET1_INTRQP(netif);
    n=(off_t)nb_size(netbuf);
    s=splimp();
    if (!IF_QFULL(ifq)) {
#ifndef FASTER_QUEUE
	/*
	 * Place interface pointer before the data
	 * for the receiving protocol.
	 */
	if (m0->m_off<=MMAXOFF&&m0->m_off>=MMINOFF+sizeof (netif_t)) {
	    m0->m_off-=sizeof (netif_t);
	    m0->m_len+=sizeof (netif_t);
	} else {
	    register struct mbuf *m;

	    MGET(m, M_DONTWAIT, MT_HEADER);
	    if (!m) goto nobufs;
	    m->m_off=MMINOFF;
	    m->m_len=sizeof (netif_t);
	    m->m_next=m0;
	    m0=m;
	}
	*mtod(m0, netif_t *)=netif;
#endif
	IF_ENQUEUE(ifq, m0);
	VENET1_PRIVATE(netif)->vp_nread+=n;
	if (VENET1_PRIVATE(netif)->vp_selr) {
	    (void)selwakeup(VENET1_PRIVATE(netif)->vp_selr,
		VENET1_STATE(netif)&ET1_RCOLL);
#ifdef MACH_USER_API
	    selthreadclear(&VENET1_PRIVATE(netif)->vp_selr);
#else
	    VENET1_PRIVATE(netif)->vp_selr=THREAD_NULL;
#endif
	    VENET1_STATE_BIC(netif, ET1_RCOLL);
	}
	if (VENET1_STATE(netif)&ET1_WAKE) {
	    VENET1_STATE_BIC(netif, ET1_WAKE);
	    wakeup((caddr_t)ifq);
	}
	if (VENET1_STATE(netif)&ET1_ASYNC) {
	    register struct proc *p;
	    register short pgrp;

	    pgrp=VENET1_PGRP(netif);
	    if (pgrp<0) gsignal(-pgrp, SIGIO);
	    else if (pgrp>0&&(p=pfind(pgrp))!=0) psignal(p, SIGIO);
#ifdef NOTDEF
	    VENET1_STATE_BIC(netif, ET1_ASYNC);	/* hmm... */
#endif
	}
    }
    else {
	IF_DROP(ifq);
#ifndef FASTER_QUEUE
    nobufs:
#endif
	m_freem(m0);
    }
    (void)splx(s);
}
#ifdef SEGREGATE

/* "It's like deja vu all over again!" */
static void
et1_aqueue(netif_t netif, netbuf_t netbuf) {
    register struct mbuf *m0;
    register struct ifqueue *ifq;
    register int s;
    off_t n;

    m0=(struct mbuf *)netbuf;	/* netbufs are really loaned mbufs! */
    ifq=VENET1_AINTRQP(netif);
    n=(off_t)nb_size(netbuf);
    s=splimp();
    if (!IF_QFULL(ifq)) {
#ifndef FASTER_QUEUE
	/*
	 * Place interface pointer before the data
	 * for the receiving protocol.
	 */
	if (m0->m_off<=MMAXOFF&&m0->m_off>=MMINOFF+sizeof (netif_t)) {
	    m0->m_off-=sizeof (netif_t);
	    m0->m_len+=sizeof (netif_t);
	} else {
	    register struct mbuf *m;

	    MGET(m, M_DONTWAIT, MT_HEADER);
	    if (!m) goto nobufs;
	    m->m_off=MMINOFF;
	    m->m_len=sizeof (netif_t);
	    m->m_next=m0;
	    m0=m;
	}
	*mtod(m0, netif_t *)=netif;
#endif
	IF_ENQUEUE(ifq, m0);
	VENET1_PRIVATE(netif)->vp_anread+=n;
	if (VENET1_PRIVATE(netif)->vp_aselr) {
	    (void)selwakeup(VENET1_PRIVATE(netif)->vp_aselr,
		VENET1_STATE(netif)&ET1_ARCOLL);
#ifdef MACH_USER_API
	    selthreadclear(&VENET1_PRIVATE(netif)->vp_aselr);
#else
	    VENET1_PRIVATE(netif)->vp_aselr=THREAD_NULL;
#endif
	    VENET1_STATE_BIC(netif, ET1_ARCOLL);
	}
	if (VENET1_STATE(netif)&ET1_AWAKE) {
	    VENET1_STATE_BIC(netif, ET1_AWAKE);
	    wakeup((caddr_t)ifq);
	}
	if (VENET1_STATE(netif)&ET1_AASYNC) {
	    register struct proc *p;
	    register short pgrp;

	    pgrp=VENET1_APGRP(netif);
	    if (pgrp<0) gsignal(-pgrp, SIGIO);
	    else if (pgrp>0&&(p=pfind(pgrp))!=0) psignal(p, SIGIO);
#ifdef NOTDEF
	    VENET1_STATE_BIC(netif, ET1_AASYNC);	/* hmm... */
#endif
	}
    }
    else {
	IF_DROP(ifq);
#ifndef FASTER_QUEUE
    nobufs:
#endif
	m_freem(m0);
    }
    (void)splx(s);
}
#endif /* SEGREGATE */
/*
 * END: BSD copyrighted material
 */

static int
venet1_input(netif_t ifp, netif_t rifp, netbuf_t nb, void *extra)
{
    unsigned short etype;

    /* Do we want packets from this driver? */
    if (VENET1_RIF(ifp)!=rifp) return (EAFNOSUPPORT);

    /*
     * Check fields in the packet to see whether they match
     * the protocol we understand.
     */
    nb_read(nb, ETYPEOFFSET, sizeof(etype), &etype);
    etype=ntohs(etype);
#ifdef NOTDEF
    /*
     * We don't handle ethernet trailer protocol.
     */
    if (etype >= ETHERTYPE_TRAIL &&
	etype < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER)
	return (EAFNOSUPPORT);
    /* Nor 802.3 crap */
    if ((unsigned)etype<=1500) return (EAFNOSUPPORT);
#endif
    switch (etype) {
#ifdef SEGREGATE
    case ETHERTYPE_ET1:
	/* if no one has us open, pretend we're not here */
	switch (VENET1_STATE(ifp)&(ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN)) {
	case ET1_ISOPEN:
	case ET1_ISOPEN|ET1_EOPEN:
	case ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN:
	    if_ipackets_set(ifp, if_ipackets(ifp) + 1);
	    et1_queue(ifp, nb);
	    break;
	default:
	    return(EAFNOSUPPORT);
	}
	break;
    case ETHERTYPE_AARP:
	/* if no one has us open, pretend we're not here */
	switch (VENET1_STATE(ifp)&(ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN)) {
	case ET1_ISOPEN:
	    if_ipackets_set(ifp, if_ipackets(ifp) + 1);
	    et1_queue(ifp, nb);
	    break;
	case ET1_ISOPEN|ET1_AOPEN:
	case ET1_ISOPEN|ET1_EOPEN|ET1_AOPEN:
	    if_ipackets_set(ifp, if_ipackets(ifp) + 1);
	    et1_aqueue(ifp, nb);
	    break;
	default:
	    return(EAFNOSUPPORT);
	}
	break;
#else
    case ETHERTYPE_ET1:
    case ETHERTYPE_AARP:
	/* if no one has us open, pretend we're not here */
	if (!(VENET1_STATE(ifp)&ET1_ISOPEN)) return(EAFNOSUPPORT);
	if_ipackets_set(ifp, if_ipackets(ifp) + 1);
	et1_queue(ifp, nb);
	break;
#endif /* SEGREGATE */
    default:
	/*
	 * Do not free buf: let others handle it
	 */
	return (EAFNOSUPPORT);
    }
    return (0);
}

/* Instantiate a new minor to correspond to a real device*/
static void
venet1_attach(void *private, netif_t rifp)
{
    netif_t ifp;
    const char *name;
    unsigned int unit;
    void *ifprivate;
    int s;

    if (strcmp(if_type(rifp), IFTYPE_ETHERNET)!=0) return;

    if (instance.et1nif<ET1_MAXMIN) {
	ifprivate=(void *)kalloc(sizeof(venet1_private_t));
	bzero(ifprivate, sizeof(venet1_private_t));
	name=if_name(rifp);
	unit=if_unit(rifp);
	ifp=if_attach(NULL, venet1_input, venet1_output,
	    venet1_getbuf, venet1_control, name, unit, IFTYPE_ET1,
	    if_mtu(rifp), IFF_BROADCAST|IFF_NOTRAILERS, NETIFCLASS_VIRTUAL,
	    ifprivate);

	((venet1_private_t *)if_private(ifp))->vp_rifp=rifp;
	((venet1_private_t *)if_private(ifp))->vp_intrq.ifq_maxlen=et1qmaxlen;
#ifdef SEGREGATE
	((venet1_private_t *)if_private(ifp))->vp_aintrq.ifq_maxlen=et1qmaxlen;
#endif

	if_control(rifp, IFCONTROL_GETADDR, VENET1_ENADDRP(ifp));

	s=splimp();
	if_flags_set(ifp, if_flags(ifp)|IFF_UP);
	(void)splx(s);

	instance.et1if[instance.et1nif++]=ifp;

	if (et1verbose) printf(
	    "ET1 protocol access enabled for interface %s%u, type \"%s\"\n",
	    name, unit, IFTYPE_ETHERNET);
    }
    return;
}

void
venet1_config(void)
{
    /*
     * You can't use if_registervirtual() in any protocol module
     * that can be unloaded, otherwise the next time you load a
     * network device driver, if_attach() will attempt to call
     * your [nonexistent] attach routine, with disastrous
     * results.  3.0's "fix" is the addition of the DETACH
     * Load Command, but what's really needed is an
     * if_unregistervirtual().
     */
#ifndef NOTDEF
#ifdef MACH_USER_API
    register netif_t ni;
#else
    register struct ifnet *ifp;
#endif
#endif

    instance.et1nif=0;
#ifdef NOTDEF
    if_registervirtual(venet1_attach, NULL);
#else
    /*
     * Call back our attach routine for each network interface,
     * but don't register.  This means that we can't connect with
     * any network devices that come along later, but that's
     * better than crashing.
     */
#ifdef MACH_USER_API
    for (ni=iflist_first();ni;ni=iflist_next(ni))
	if (if_class(ni)==NETIFCLASS_REAL) venet1_attach(NULL, ni);
#else
    for (ifp=ifnet;ifp;ifp=ifp->if_next)
	if (ifp->if_class==NETIFCLASS_REAL)
	venet1_attach(NULL, (netif_t)ifp);
#endif
#endif
}

void
venet1_detach(void)
{
#ifdef MACH_USER_API
    netif_t ni;

    /* 3.0 recycles used netifs */
    for (ni=iflist_first();ni;) {
	netif_t nin;
	nin=iflist_next(ni);
	if (if_type(ni)==IFTYPE_ET1) {
	    if (et1verbose)
		printf("ET1 protocol access disabled for interface %s%u.\n",
		if_name(ni), if_unit(ni));
	    kfree(if_private(ni), sizeof(venet1_private_t));
	    if_detach(ni);
	}
	ni=nin;
    }
#else
    register struct ifnet **ifpp;
    int s;

    /* pre-3.0 is a bit more fragile... */
    s=splimp();
    for (ifpp= &ifnet;*ifpp;) {
	register struct ifnet *ifp;
	ifp= *ifpp;
	if (ifp->if_type==IFTYPE_ET1) {
	    if (et1verbose)
		printf("ET1 protocol access disabled for interface %s%u.\n",
		if_name((netif_t)ifp), if_unit((netif_t)ifp));
	    *ifpp=ifp->if_next;
	    kfree(if_private((netif_t)ifp), sizeof(venet1_private_t));
	    kfree((void *)ifp, sizeof *ifp);
	}
	else ifpp= &ifp->if_next;
    }
    (void)splx(s);
#endif
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.