Source to arch/i386/isa/if_ec.c


Enter a symbol's name here to quickly find it.

/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Tim L. Tucker.
 *
 * 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
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, 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 University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR 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.
 *
 *	if_ec.c,v 1.10 1993/06/14 16:45:25 mycroft Exp
 */

/*
 * A driver for the 3Com 3C503 (Etherlink II) ethernet adaptor.
 *
 *   Written by Herb Peyerl ([email protected]) on 04/25/92.
 *   (This is my first ever device driver and I couldn't have done
 *    it without the consumption of many "Brock Gummy Bears" so a
 *    big thanx to the "Brock Candy Company" of Chattanooga TN)
 *
 *   This driver uses the Western Digital 8003 driver for a template
 *   since the two cards use the DP8390 chip by National.  Everything
 *   is fairly similar except that the nic on the wd8003 appears to
 *   to see shared memory at 0x0000 and on the 3com, the nic sees it 
 *   at 0x2000.  Also, the 3c503 has it's own ASIC for controlling 
 *   things like the IRQ level in software and whether to use the
 *   onboard xceiver or not.  Since the Clarkson drivers do a very
 *   good rendition of a 3c503, I also scavenged a lot of ideas from
 *   there.
 */
#include "param.h"
#include "mbuf.h"
#include "socket.h"
#include "ioctl.h"
#include "errno.h"
#include "syslog.h"
#include "net/if.h"
#include "net/netisr.h"
#ifdef INET
#include "netinet/in.h"
#include "netinet/in_systm.h"
#include "netinet/in_var.h"
#include "netinet/ip.h"
#include "netinet/if_ether.h"
#endif
#ifdef NS
#include "netns/ns.h"
#include "netns/ns_if.h"
#endif
#include "i386/isa/isa_device.h"
#include "i386/isa/if_ec.h"

#include "ec.h"

/*
 * Ethernet software status per interface.
 *
 * Each interface is referenced by a network interface structure,
 * qe_if, which the routing code uses to locate the interface.
 * This structure contains the output queue for the interface, its address, ...
 */
struct	ec_softc {
	struct	arpcom ec_ac;		/* Ethernet common part 	*/
#define	ec_if	ec_ac.ac_if		/* network-visible interface 	*/
#define	ec_addr	ec_ac.ac_enaddr		/* hardware Ethernet address 	*/
#define ns_addrp ec_ac.ac_enaddr	/* hardware Ethernet address (for ns)*/
	u_char	ec_flags;		/* software state		*/
#define	EC_RUNNING	0x01
#define EC_TXBUSY	0x02

	u_char	ec_type;		/* interface type code		*/
	u_short	ec_vector;		/* interrupt vector 		*/
	short	ec_io_ctl_addr;		/* i/o bus address, control	*/
	short	ec_io_nic_addr;		/* i/o bus address, DS8390	*/

	caddr_t	ec_vmem_addr;		/* card RAM virtual memory base */
	u_long	ec_vmem_size;		/* card RAM bytes		*/
	caddr_t	ec_vmem_ring;		/* receive ring RAM vaddress	*/
	caddr_t	ec_vmem_end;		/* receive ring RAM end	*/
} ec_softc[NEC];

#define PAGE0  outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE0);
#define PAGE1  outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE1);
static Bdry;

int ether_output(),
    ecprobe(),
    ecattach(),
    ecintr(),
    ec_init(),
    ec_ioctl(),
    ec_reset(),
    ec_watchdog(),
    ec_start_output();

struct isa_driver ecdriver = {
	ecprobe, 
        ecattach,
        "ec",
};

ecprobe(is)
struct isa_device *is;
{
	register struct ec_softc *sc = &ec_softc[is->id_unit&127];
	int i, sum; 
/*
 * Set up the softc structure with card specific info.
 * The 3Com asic is at base+0x400
 */
	sc->ec_io_ctl_addr = is->id_iobase + 0x400;
	sc->ec_io_nic_addr = is->id_iobase;
	sc->ec_vector = is->id_irq;
	sc->ec_vmem_addr = (caddr_t)is->id_maddr;
	sc->ec_vmem_size = is->id_msize;
	sc->ec_vmem_ring = sc->ec_vmem_addr + (EC_PAGE_SIZE * EC_TXBUF_SIZE);
	sc->ec_vmem_end = sc->ec_vmem_addr + is->id_msize;
/* 
 * Now we get the MAC address. Assume thin ethernet unless told otherwise later.
 */
	/* Toggle reset bit on*/
	outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_RESET|ECNTRL_ONBOARD);
	DELAY(100);
	/* Toggle reset bit off */
	outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_ONBOARD);
	DELAY(100);
	/* Map SA_PROM */
	outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_SAPROM|ECNTRL_ONBOARD);
	for (i=0;i<ETHER_ADDR_LEN; ++i)
		sc->ec_addr[i] = inb(sc->ec_io_nic_addr + i);
	/* Disable SA_PROM */
	outb(sc->ec_io_ctl_addr + E33G_CNTRL, ECNTRL_ONBOARD);
	/* tcm, rsel, mbs0, nim */
	outb(sc->ec_io_ctl_addr + E33G_GACFR, EGACFR_IRQOFF);
/*
 * Stop the chip just in case.
 */
	DELAY(1000);
	PAGE0
	outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_STOP);
	DELAY(1000);

	/* Check cmd reg and fail if not right */
	if ((i=inb(sc->ec_io_nic_addr + EN_CCMD)) != (ENC_NODMA|ENC_STOP))
		return(0);

/*
 * Test the shared memory.
 */
	for(i = 0 ; i < sc->ec_vmem_size ; ++i)
		sc->ec_vmem_addr[i] = 0x0;
	for(sum=0, i=0; i<sc->ec_vmem_size; ++i)
		sum += sc->ec_vmem_addr[i];
	if(sum)
	{
		printf("ec%d: shared memory error (device conflict?)\n",
			is->id_unit);
		return(0);
	}
/*
 *  All done. 
 */
	return(16);
}

ecattach(is)
struct isa_device *is;
{
	register struct ec_softc *sc = &ec_softc[is->id_unit];
	register struct ifnet *ifp = &sc->ec_if;

/**
 ** Initialize the ASIC in same order as Clarkson driver.
 **/


/*
 * Point vector pointer registers off into boonies.
 */
	outb(sc->ec_io_ctl_addr + E33G_VP2, 0xff);
	outb(sc->ec_io_ctl_addr + E33G_VP1, 0xff);
	outb(sc->ec_io_ctl_addr + E33G_VP0, 0x0);
/*
 * Set up control of shared memory, buffer ring, etc.
 */
	outb(sc->ec_io_ctl_addr + E33G_STARTPG, EC_RXBUF_OFFSET);
	outb(sc->ec_io_ctl_addr + E33G_STOPPG, EC_RXBUF_END);
/*
 * Set up the IRQ  and NBURST on the board.
 *  ( Not sure why we set up NBURST since we don't use DMA.) 
 *
 * Normally we would is->id_irq<<2 but IRQ2 is defined as 0x200
 * in icu.h so it's a special case.
 */
	if(is->id_irq == 0x200)
		outb(sc->ec_io_ctl_addr + E33G_IDCFR, 0x10);
	else
		outb(sc->ec_io_ctl_addr + E33G_IDCFR, is->id_irq << 2);
        outb(sc->ec_io_ctl_addr + E33G_NBURST, 0x08);     /* Set Burst to 8 */
	outb(sc->ec_io_ctl_addr + E33G_DMAAH, 0x20);
	outb(sc->ec_io_ctl_addr + E33G_DMAAL, 0x0);
/*
 * Fill in the ifnet structure.
 */
	ifp->if_unit = is->id_unit;
	ifp->if_name = "ec" ;
	ifp->if_mtu = ETHERMTU;
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS;
	ifp->if_init = ec_init;
	ifp->if_output = ether_output;
	ifp->if_start = ec_start_output;
	ifp->if_ioctl = ec_ioctl;
	ifp->if_reset = ec_reset;
	ifp->if_watchdog = ec_watchdog;
/*
 * Attach the interface to something. Have to figure this out later.
 */
	if_attach(ifp);
/*
 * Weeee.. We get to tell people we exist...
 */
	printf("ec%d: ethernet address %s\n",
		is->id_unit, ether_sprintf(sc->ec_addr));
}

ec_init(unit)
int unit;
{
	register struct ec_softc *sc = &ec_softc[unit];
	register struct ifnet *ifp = &sc->ec_if;
	int i, s;
	u_short   ax, cx;

	Bdry=0;
/*
 * Address not known.
 */
	if(ifp->if_addrlist == (struct ifaddr *) 0)
		return;

	/*
	 * select thick (e.g. AUI connector) if LLC0 bit is set
	 */
	outb(sc->ec_io_ctl_addr + E33G_CNTRL,
		(ifp->if_flags & IFF_LLC0) ? 0 : ECNTRL_ONBOARD);

/*
 * Set up the 8390 chip.
 *   (Use sequence recommended by 3Com. )
 */
	s=splhigh();
	PAGE0
	outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_PAGE0|ENC_STOP);
	outb(sc->ec_io_nic_addr + EN0_DCFG, ENDCFG_BM8);
	outb(sc->ec_io_nic_addr + EN0_RCNTLO, 0x0);
	outb(sc->ec_io_nic_addr + EN0_RCNTHI, 0x0);
	outb(sc->ec_io_nic_addr + EN0_RXCR, ENRXCR_MON );
	outb(sc->ec_io_nic_addr + EN0_TXCR, 0x02);
	outb(sc->ec_io_nic_addr + EN0_BOUNDARY, EC_RXBUF_OFFSET);
	outb(sc->ec_io_nic_addr + EN0_TPSR, 0x20);
	outb(sc->ec_io_nic_addr + EN0_STARTPG, EC_RXBUF_OFFSET);
	outb(sc->ec_io_nic_addr + EN0_STOPPG, EC_RXBUF_END);
	outb(sc->ec_io_nic_addr + EN0_ISR, 0xff);
	outb(sc->ec_io_nic_addr + EN0_IMR, 0x3f);
/*
 * Copy Ethernet address from SA_PROM into 8390 chip registers.
 */
	PAGE1
	for(i=0;i<6;i++)
		outb(sc->ec_io_nic_addr + EN1_PHYS+i, sc->ec_addr[i]);
/*
 * Set multicast filter mask bits in case promiscuous rcv wanted (???)
 *   (set to 0xff as in if_we.c)
 */
	for(i=0;i<8;i++)
		outb(sc->ec_io_nic_addr + EN1_MULT+i, 0xff);
/*
 * Set current shared page for RX to work on.
 */
	outb(sc->ec_io_nic_addr + EN1_CURPAG, EC_RXBUF_OFFSET);
/*
 * Start the 8390. Clear Interrupt Status reg, and accept Broadcast
 * packets.
 */
	outb(sc->ec_io_nic_addr + EN_CCMD, ENC_START|ENC_PAGE0|ENC_NODMA);
	outb(sc->ec_io_nic_addr + EN0_ISR, 0xff);
	outb(sc->ec_io_nic_addr + EN0_TXCR, 0x0);
	outb(sc->ec_io_nic_addr + EN0_RXCR, ENRXCR_BCST);
/*
 * Take interface out of reset, program the vector,
 * enable interrupts, and tell the world we are up.
 */
	ifp->if_flags |= IFF_RUNNING;
	outb(sc->ec_io_ctl_addr + E33G_GACFR, EGACFR_NORM);  /* tcm, rsel, mbs0 */
	(void) splx(s);
	sc->ec_flags &= ~EC_TXBUSY;
	ec_start_output(ifp);
}

ec_start_output(ifp)
struct ifnet *ifp;
{
	register struct ec_softc *sc = &ec_softc[ifp->if_unit];
	struct mbuf *m0, *m;
	register caddr_t buffer;
	int len, s;
	int ec_cmd_reg;

/*
 * The DS8390 only has one transmit buffer, if it is busy we
 * must wait until the transmit interrupt completes.
 */
	s=splhigh();
	if(sc->ec_flags & EC_TXBUSY)
	{
		(void) splx(s);
		return;
	}
	IF_DEQUEUE(&sc->ec_if.if_snd, m);
	if(m == 0)
	{
		(void) splx(s);
		return;
	}
	sc->ec_flags |= EC_TXBUSY;
	(void) splx(s);

/*
 * Copy the mbuf chain into the transmit buffer
 */
	buffer = sc->ec_vmem_addr;
	len = 0;
	for(m0 = m; m!= 0; m = m->m_next)
	{
		bcopy(mtod(m, caddr_t), buffer, m->m_len);
		buffer += m->m_len;
		len  += m->m_len;
	}
	m_freem(m0);

/*
 * Init transmit length registers and set transmit start flag.
 */
	s=splhigh();
	len = MAX(len, ETHER_MIN_LEN);
	PAGE0
	outb(sc->ec_io_nic_addr + EN0_TCNTLO, len & 0xff);
	outb(sc->ec_io_nic_addr + EN0_TCNTHI, len >> 8);
	ec_cmd_reg = inb(sc->ec_io_nic_addr + EN_CCMD);
	outb(sc->ec_io_nic_addr + EN_CCMD, ec_cmd_reg|ENC_TRANS);
	(void) splx(s);
}

	int ec_cmd_reg, ec_sts_reg;
/*
 * Interrupt handler.
 */
ecintr(unit)
int unit;
{
	register struct ec_softc *sc = &ec_softc[unit];

	unit = 0;

/*
 * Get current command register and interrupt status.
 * Turn off interrupts while we take care of things.
 */
	ec_cmd_reg = inb(sc->ec_io_nic_addr + EN_CCMD);
	PAGE0
	ec_sts_reg = inb(sc->ec_io_nic_addr + EN0_ISR);
	outb(sc->ec_io_nic_addr + EN0_IMR, 0x0);
loop:
	outb(sc->ec_io_nic_addr + EN0_ISR, ec_sts_reg);
	/*
	 * have we lost ourselves somewhere?
	 */
	if (ec_sts_reg == 0xff)
		ec_watchdog(unit);
/*
 * Transmit error
 */
 	if(ec_sts_reg & ENISR_TX_ERR)
	{
		sc->ec_if.if_collisions += inb(sc->ec_io_nic_addr + EN0_TCNTLO);
		++sc->ec_if.if_oerrors;
	}
/*
 * Receive Error
 */
	if(ec_sts_reg & ENISR_RX_ERR)
	{
		(void) inb(sc->ec_io_nic_addr + 0xD);
		(void) inb(sc->ec_io_nic_addr + 0xE);
		(void) inb(sc->ec_io_nic_addr + 0xF);
		++sc->ec_if.if_ierrors;
	}
/*
 * Normal transmit complete.
 */
	if(ec_sts_reg&ENISR_TX || ec_sts_reg&ENISR_TX_ERR) 
		ectint(unit);
/*
 * Normal receive notification
 */
	if(ec_sts_reg&(ENISR_RX|ENISR_RX_ERR)) 
		ecrint(unit);

/*
 * Try to start transmit
 */
	ec_start_output(&sc->ec_if);
/*
 * Reenable  onboard interrupts.
 */
	/*PAGE0*/
	outb(sc->ec_io_nic_addr + EN_CCMD, ec_cmd_reg);
	outb(sc->ec_io_nic_addr + EN0_IMR, 0x3f);
	if(ec_sts_reg=inb(sc->ec_io_nic_addr + EN0_ISR))
		goto loop;
}

/*
 * Transmit interrupt
 */
ectint(unit)
int unit;
{
	register struct ec_softc *sc = &ec_softc[unit];
/*
 * Do some statistics.
 */
	PAGE0
	sc->ec_flags &= ~EC_TXBUSY;
	sc->ec_if.if_timer = 0;
	++sc->ec_if.if_opackets;
	sc->ec_if.if_collisions += inb(sc->ec_io_nic_addr + EN0_TCNTLO);
}

/*
 * Receive interrupt.
 * (This gave me the most trouble so excuse the mess.)
 */
ecrint(unit)
int unit;
{
	register struct ec_softc *sc = &ec_softc[unit];
	u_char bnry, curr;
	int len;
	struct ec_ring *ecr;
 
	/*
	 * Traverse the receive ring looking for packets to pass back.
	 * The search is complete when we find a descriptor not in use.
	 */
	PAGE0
	bnry = inb(sc->ec_io_nic_addr + EN0_BOUNDARY);
	PAGE1
	curr = inb(sc->ec_io_nic_addr + EN1_CURPAG);
if(Bdry)
	bnry =Bdry;

	while (bnry != curr)
	{
		/* get pointer to this buffer header structure */
		ecr = (struct ec_ring *)(sc->ec_vmem_addr + ((bnry-EC_VMEM_OFFSET) << 8));

		/* count includes CRC */
		len = ecr->ec_count - 4;
		/*if (len > 30 && len <= ETHERMTU+100) */
			ecread(sc, (caddr_t)(ecr + 1), len);
		/*else printf("reject:%x bnry:%x curr:%x", len, bnry, curr);*/
outofbufs:
		PAGE0
		/* advance on chip Boundry register */
		if((caddr_t) ecr + EC_PAGE_SIZE - 1 > sc->ec_vmem_end) {
			bnry = EC_RXBUF_OFFSET;
			outb(sc->ec_io_nic_addr + EN0_BOUNDARY,
				(sc->ec_vmem_size / EC_PAGE_SIZE) - 1 - EC_VMEM_OFFSET); 
		} else {
			if (len > 30 && len <= ETHERMTU+100)
				bnry = ecr->ec_next_packet;
			else bnry = curr;

			/* watch out for NIC overflow, reset Boundry if invalid */
			if ((bnry - 1) < EC_RXBUF_OFFSET) {
		    		outb(sc->ec_io_nic_addr + EN0_BOUNDARY, 
					(sc->ec_vmem_size / EC_PAGE_SIZE) - 1 - EC_VMEM_OFFSET);
				bnry = EC_RXBUF_OFFSET;
			} else
				outb(sc->ec_io_nic_addr + EN0_BOUNDARY, bnry-1);
		}

		/* refresh our copy of CURR */
		PAGE1
		curr = inb(sc->ec_io_nic_addr + EN1_CURPAG);
	}
	Bdry = bnry;
	PAGE0
}

#define	ecdataaddr(sc, eh, off, type) \
	((type) ((caddr_t)((eh)+1)+(off) >= (sc)->ec_vmem_end) ? \
		(((caddr_t)((eh)+1)+(off))) - (sc)->ec_vmem_end \
		+ (sc)->ec_vmem_ring: \
		((caddr_t)((eh)+1)+(off)))

ecread(sc, buf, len)
register struct ec_softc *sc;
char *buf;
int len;
{
	register struct ether_header *eh;
    	struct mbuf *m, *ecget();
	int off, resid;

	++sc->ec_if.if_ipackets;
	/*
	 * Deal with trailer protocol: if type is trailer type
	 * get true type from first 16-bit word past data.
	 * Remember that type was trailer by setting off.
	 */
	eh = (struct ether_header *)buf;
	eh->ether_type = ntohs((u_short)eh->ether_type);
	if (eh->ether_type >= ETHERTYPE_TRAIL &&
	    eh->ether_type < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) {
		off = (eh->ether_type - ETHERTYPE_TRAIL) * 512;
		if (off >= ETHERMTU) return;		/* sanity */
		eh->ether_type = ntohs(*ecdataaddr(sc, eh, off, u_short *));
		resid = ntohs(*(ecdataaddr(sc, eh, off+2, u_short *)));
		if (off + resid > len) return;		/* sanity */
		len = off + resid;
	} else	off = 0;

	len -= sizeof(struct ether_header);
	if (len <= 0) return;

	/*
	 * Pull packet off interface.  Off is nonzero if packet
	 * has trailing header; neget will then force this header
	 * information to be at the front, but we still have to drop
	 * the type and length which are at the front of any trailer data.
	 */
	m = ecget(buf, len, off, &sc->ec_if, sc);
	if (m == 0) return;
	ether_input(&sc->ec_if, eh, m);
}
ec_ioctl(ifp, cmd, data)
register struct ifnet *ifp;
int cmd;
caddr_t data;
{
	register struct ifaddr *ifa = (struct ifaddr *)data;
	struct ec_softc *sc = &ec_softc[ifp->if_unit];
	struct ifreq *ifr = (struct ifreq *)data;
	int s=splimp(), error=0;
	switch(cmd){
	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;
		switch (ifa->ifa_addr->sa_family) {
#ifdef INET
		case AF_INET:
			ec_init(ifp->if_unit);	/* before arpwhohas */
			((struct arpcom *)ifp)->ac_ipaddr =
				IA_SIN(ifa)->sin_addr;
			arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr);
			break;
#endif
#ifdef NS
		case AF_NS:
		    {
			register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr);

			if (ns_nullhost(*ina))
				ina->x_host = *(union ns_host *)(sc->ns_addrp);
			else {
				/* 
				 * The manual says we can't change the address 
				 * while the receiver is armed,
				 * so reset everything
				 */
				ifp->if_flags &= ~IFF_RUNNING; 
				bcopy((caddr_t)ina->x_host.c_host,
				    (caddr_t)sc->ns_addrp, sizeof(sc->ns_addrp));
			}
			ec_init(ifp->if_unit); /* does ne_setaddr() */
			break;
		    }
#endif
		default:
			ec_init(ifp->if_unit);
			break;
		}
		break;

	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == 0 &&
		    ifp->if_flags & IFF_RUNNING) {
			ifp->if_flags &= ~IFF_RUNNING;
			ec_stop(ifp->if_unit);
		} else if (ifp->if_flags & IFF_UP &&
		    (ifp->if_flags & IFF_RUNNING) == 0)
			ec_init(ifp->if_unit);
		break;

#ifdef notdef
	case SIOCGHWADDR:
		bcopy((caddr_t)sc->sc_addr, (caddr_t) &ifr->ifr_data,
			sizeof(sc->sc_addr));
		break;
#endif

	default:
		error = EINVAL;
	}
	splx(s);
	return (error);
}

ec_reset(unit)
int unit;
{
	if(unit >= NEC)
		return;
	printf("ec%d: reset\n", unit);
	ec_init(unit);
}

ec_watchdog(unit)
int unit;
{
	log(LOG_WARNING, "ec%d: soft reset\n", unit);
	ec_stop(unit);
	ec_init(unit);
}

ec_stop(unit)
int unit;
{
	register struct ec_softc *sc = &ec_softc[unit];
	int s;

	s=splimp();
	PAGE0
	outb(sc->ec_io_nic_addr + EN_CCMD, ENC_NODMA|ENC_STOP);
	outb(sc->ec_io_nic_addr + EN0_IMR, 0x0);
	sc->ec_flags &= ~EC_RUNNING;
/*
 * Shutdown the 8390.
 */
	(void) splx(s);
}

/*
 * Pull read data off a interface.
 * Len is length of data, with local net header stripped.
 * Off is non-zero if a trailer protocol was used, and
 * gives the offset of the trailer information.
 * We copy the trailer information and then all the normal
 * data into mbufs.  When full cluster sized units are present
 * we copy into clusters.
 */
struct mbuf *
ecget(buf, totlen, off0, ifp, sc)
	caddr_t buf;
	int totlen, off0;
	struct ifnet *ifp;
	struct ec_softc *sc;
{
	struct mbuf *top, **mp, *m, *p;
	int off = off0, len;
	register caddr_t cp = buf;
	char *epkt;
	int tc =totlen;

	buf += sizeof(struct ether_header);
	cp = buf;
	epkt = cp + totlen;

	if (off) {
		cp += off + 2 * sizeof(u_short);
		totlen -= 2 * sizeof(u_short);
	}

	MGETHDR(m, M_DONTWAIT, MT_DATA);
	if (m == 0)
		return (0);
	m->m_pkthdr.rcvif = ifp;
	m->m_pkthdr.len = totlen;
	m->m_len = MHLEN;

	top = 0;
	mp = &top;
	while (totlen > 0) {
		if (top) {
			MGET(m, M_DONTWAIT, MT_DATA);
			if (m == 0) {
				m_freem(top);
				return (0);
			}
			m->m_len = MLEN;
		}
		len = min(totlen, epkt - cp);
		if (len >= MINCLSIZE) {
			MCLGET(m, M_DONTWAIT);
			if (m->m_flags & M_EXT)
				m->m_len = len = min(len, MCLBYTES);
			else
				len = m->m_len;
		} else {
			/*
			 * Place initial small packet/header at end of mbuf.
			 */
			if (len < m->m_len) {
				if (top == 0 && len + max_linkhdr <= m->m_len)
					m->m_data += max_linkhdr;
				m->m_len = len;
			} else
				len = m->m_len;
		}

		totlen -= len;
		/* only do up to end of buffer */
		if (cp+len > sc->ec_vmem_end) {
			unsigned toend = sc->ec_vmem_end - cp;

			bcopy(cp, mtod(m, caddr_t), toend);
			cp = sc->ec_vmem_ring;
			bcopy(cp, mtod(m, caddr_t)+toend, len - toend);
			cp += len - toend;
			epkt = cp + totlen;
		} else {
			bcopy(cp, mtod(m, caddr_t), (unsigned)len);
			cp += len;
		}
		*mp = m;
		mp = &m->m_next;
		if (cp == epkt) {
			cp = buf;
			epkt = cp + tc;
		}
	}
	return (top);
}