1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-27 11:55:06 +00:00
freebsd/sys/i386/isa/if_le.c

1996 lines
54 KiB
C
Raw Normal View History

/*-
* Copyright (c) 1994 Matt Thomas (thomas@lkg.dec.com)
* All rights reserved.
*
* 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. The name of the author may not be used to endorse or promote products
* derived from this software withough specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*
1999-08-28 01:08:13 +00:00
* $FreeBSD$
*/
/*
* DEC EtherWORKS 2 Ethernet Controllers
* DEC EtherWORKS 3 Ethernet Controllers
*
* Written by Matt Thomas
* BPF support code stolen directly from if_ec.c
*
* This driver supports the DEPCA, DE100, DE101, DE200, DE201,
* DE2002, DE203, DE204, DE205, and DE422 cards.
*/
#include "le.h"
#include "opt_inet.h"
#include "opt_ipx.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/icu.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <net/bpf.h>
#ifndef COMPAT_OLDISA
#error "The le device requires the old isa compatibility shims"
#endif
/* Forward declarations */
typedef struct le_softc le_softc_t;
typedef struct le_board le_board_t;
typedef u_short le_mcbits_t;
#define LE_MC_NBPW_LOG2 4
#define LE_MC_NBPW (1 << LE_MC_NBPW_LOG2)
#define IF_RESET_ARGS int unit
#define LE_RESET(ifp) (((sc)->if_reset)((sc)->le_if.if_unit))
#if !defined(LE_NOLEMAC)
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Start of DEC EtherWORKS III (LEMAC) dependent structures
*
*/
1997-08-25 22:28:57 +00:00
#include <i386/isa/ic/lemac.h> /* Include LEMAC definitions */
static int lemac_probe(le_softc_t *sc, const le_board_t *bd, int *msize);
struct le_lemac_info {
u_int lemac__lastpage; /* last 2K page */
u_int lemac__memmode; /* Are we in 2K, 32K, or 64K mode */
u_int lemac__membase; /* Physical address of start of RAM */
u_int lemac__txctl; /* Transmit Control Byte */
u_int lemac__txmax; /* Maximum # of outstanding transmits */
le_mcbits_t lemac__mctbl[LEMAC_MCTBL_SIZE/sizeof(le_mcbits_t)];
/* local copy of multicast table */
u_char lemac__eeprom[LEMAC_EEP_SIZE]; /* local copy eeprom */
char lemac__prodname[LEMAC_EEP_PRDNMSZ+1]; /* prodname name */
#define lemac_lastpage le_un.un_lemac.lemac__lastpage
#define lemac_memmode le_un.un_lemac.lemac__memmode
#define lemac_membase le_un.un_lemac.lemac__membase
#define lemac_txctl le_un.un_lemac.lemac__txctl
#define lemac_txmax le_un.un_lemac.lemac__txmax
#define lemac_mctbl le_un.un_lemac.lemac__mctbl
#define lemac_eeprom le_un.un_lemac.lemac__eeprom
#define lemac_prodname le_un.un_lemac.lemac__prodname
};
#endif /* !defined(LE_NOLEMAC) */
#if !defined(LE_NOLANCE)
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Start of DEC EtherWORKS II (LANCE) dependent structures
*
*/
1997-08-25 22:28:57 +00:00
#include <i386/isa/ic/am7990.h>
#ifndef LN_DOSTATS
#define LN_DOSTATS 1
#endif
static int depca_probe(le_softc_t *sc, const le_board_t *bd, int *msize);
typedef struct lance_descinfo lance_descinfo_t;
typedef struct lance_ring lance_ring_t;
typedef unsigned lance_addr_t;
struct lance_descinfo {
caddr_t di_addr; /* address of descriptor */
lance_addr_t di_bufaddr; /* LANCE address of buffer owned by descriptor */
unsigned di_buflen; /* size of buffer owned by descriptor */
struct mbuf *di_mbuf; /* mbuf being transmitted/received */
};
struct lance_ring {
lance_descinfo_t *ri_first; /* Pointer to first descriptor in ring */
lance_descinfo_t *ri_last; /* Pointer to last + 1 descriptor in ring */
lance_descinfo_t *ri_nextin; /* Pointer to next one to be given to HOST */
lance_descinfo_t *ri_nextout; /* Pointer to next one to be given to LANCE */
unsigned ri_max; /* Size of Ring - 1 */
unsigned ri_free; /* Number of free rings entires (owned by HOST) */
lance_addr_t ri_heap; /* Start of RAM for this ring */
lance_addr_t ri_heapend; /* End + 1 of RAM for this ring */
lance_addr_t ri_outptr; /* Pointer to first output byte */
unsigned ri_outsize; /* Space remaining for output */
};
struct le_lance_info {
unsigned lance__csr1; /* LANCE Address of init block (low 16) */
unsigned lance__csr2; /* LANCE Address of init block (high 8) */
unsigned lance__csr3; /* Copy of CSR3 */
unsigned lance__rap; /* IO Port Offset of RAP */
unsigned lance__rdp; /* IO Port Offset of RDP */
unsigned lance__ramoffset; /* Offset to valid LANCE RAM */
unsigned lance__ramsize; /* Amount of RAM shared by LANCE */
unsigned lance__rxbufsize; /* Size of a receive buffer */
ln_initb_t lance__initb; /* local copy of LANCE initblock */
ln_initb_t *lance__raminitb; /* copy to board's LANCE initblock (debugging) */
ln_desc_t *lance__ramdesc; /* copy to board's LANCE descriptors (debugging) */
lance_ring_t lance__rxinfo; /* Receive ring information */
lance_ring_t lance__txinfo; /* Transmit ring information */
#define lance_csr1 le_un.un_lance.lance__csr1
#define lance_csr2 le_un.un_lance.lance__csr2
#define lance_csr3 le_un.un_lance.lance__csr3
#define lance_rap le_un.un_lance.lance__rap
#define lance_rdp le_un.un_lance.lance__rdp
#define lance_ramoffset le_un.un_lance.lance__ramoffset
#define lance_ramsize le_un.un_lance.lance__ramsize
#define lance_rxbufsize le_un.un_lance.lance__rxbufsize
#define lance_initb le_un.un_lance.lance__initb
#define lance_raminitb le_un.un_lance.lance__raminitb
#define lance_ramdesc le_un.un_lance.lance__ramdesc
#define lance_rxinfo le_un.un_lance.lance__rxinfo
#define lance_txinfo le_un.un_lance.lance__txinfo
};
#endif /* !defined(LE_NOLANCE) */
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
1995-05-30 08:16:23 +00:00
* Start of Common Code
*
*/
static void (*le_intrvec[NLE])(le_softc_t *sc);
/*
* Ethernet status, per interface.
*/
struct le_softc {
struct arpcom le_ac; /* Common Ethernet/ARP Structure */
void (*if_init) __P((int)); /* Interface init routine */
void (*if_reset) __P((int)); /* Interface reset routine */
caddr_t le_membase; /* Starting memory address (virtual) */
unsigned le_iobase; /* Starting I/O base address */
unsigned le_irq; /* Interrupt Request Value */
unsigned le_flags; /* local copy of if_flags */
#define LE_BRDCSTONLY 0x01000000 /* If only broadcast is enabled */
u_int le_mcmask; /* bit mask for CRC-32 for multicast hash */
le_mcbits_t *le_mctbl; /* pointer to multicast table */
const char *le_prodname; /* product name DE20x-xx */
u_char le_hwaddr[6]; /* local copy of hwaddr */
union {
#if !defined(LE_NOLEMAC)
struct le_lemac_info un_lemac; /* LEMAC specific information */
#endif
#if !defined(LE_NOLANCE)
struct le_lance_info un_lance; /* Am7990 specific information */
#endif
} le_un;
};
#define le_if le_ac.ac_if
static int le_probe(struct isa_device *dvp);
static int le_attach(struct isa_device *dvp);
static ointhand2_t le_intr;
static int le_ioctl(struct ifnet *ifp, u_long command, caddr_t data);
static void le_input(le_softc_t *sc, caddr_t seg1, size_t total_len,
size_t len2, caddr_t seg2);
static void le_multi_filter(le_softc_t *sc);
static void le_multi_op(le_softc_t *sc, const u_char *mca, int oper_flg);
static int le_read_macaddr(le_softc_t *sc, int ioreg, int skippat);
#define LE_CRC32_POLY 0xEDB88320UL /* CRC-32 Poly -- Little Endian */
struct le_board {
int (*bd_probe)(le_softc_t *sc, const le_board_t *bd, int *msize);
};
1995-05-30 08:16:23 +00:00
1995-12-10 13:40:44 +00:00
static le_softc_t le_softc[NLE];
1995-12-10 13:40:44 +00:00
static const le_board_t le_boards[] = {
#if !defined(LE_NOLEMAC)
{ lemac_probe }, /* DE20[345] */
#endif
#if !defined(LE_NOLANCE)
{ depca_probe }, /* DE{20[012],422} */
#endif
{ NULL } /* Must Be Last! */
};
/*
* This tells the autoconf code how to set us up.
*/
struct isa_driver ledriver = {
INTR_TYPE_NET,
le_probe,
le_attach,
"le",
};
COMPAT_ISA_DRIVER(le, ledriver);
1995-12-10 13:40:44 +00:00
static unsigned le_intrs[NLE];
#define LE_ADDREQUAL(a1, a2) \
(((u_short *)a1)[0] == ((u_short *)a2)[0] \
|| ((u_short *)a1)[1] == ((u_short *)a2)[1] \
|| ((u_short *)a1)[2] == ((u_short *)a2)[2])
#define LE_ADDRBRDCST(a1) \
(((u_short *)a1)[0] == 0xFFFFU \
|| ((u_short *)a1)[1] == 0xFFFFU \
|| ((u_short *)a1)[2] == 0xFFFFU)
#define LE_INL(sc, reg) \
({ u_int data; \
__asm __volatile("inl %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \
data; })
#define LE_OUTL(sc, reg, data) \
({__asm __volatile("outl %0, %1"::"a" ((u_int)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));})
#define LE_INW(sc, reg) \
({ u_short data; \
__asm __volatile("inw %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \
data; })
#define LE_OUTW(sc, reg, data) \
({__asm __volatile("outw %0, %1"::"a" ((u_short)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));})
#define LE_INB(sc, reg) \
({ u_char data; \
__asm __volatile("inb %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \
data; })
#define LE_OUTB(sc, reg, data) \
({__asm __volatile("outb %0, %1"::"a" ((u_char)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));})
#define MEMCPY(to, from, len) bcopy(from, to, len)
#define MEMSET(where, what, howmuch) bzero(where, howmuch)
#define MEMCMP(l, r, len) bcmp(l, r, len)
static int
le_probe(
struct isa_device *dvp)
{
le_softc_t *sc = &le_softc[dvp->id_unit];
const le_board_t *bd;
int iospace;
if (dvp->id_unit >= NLE) {
printf("%s%d not configured -- too many devices\n",
ledriver.name, dvp->id_unit);
return 0;
}
sc->le_iobase = dvp->id_iobase;
sc->le_membase = (u_char *) dvp->id_maddr;
sc->le_irq = dvp->id_irq;
sc->le_if.if_name = ledriver.name;
sc->le_if.if_unit = dvp->id_unit;
/*
* Find and Initialize board..
*/
sc->le_flags &= ~(IFF_UP|IFF_ALLMULTI);
for (bd = le_boards; bd->bd_probe != NULL; bd++) {
if ((iospace = (*bd->bd_probe)(sc, bd, &dvp->id_msize)) != 0) {
return iospace;
}
}
return 0;
}
static int
le_attach(
struct isa_device *dvp)
{
le_softc_t *sc = &le_softc[dvp->id_unit];
struct ifnet *ifp = &sc->le_if;
dvp->id_ointr = le_intr;
ifp->if_softc = sc;
ifp->if_mtu = ETHERMTU;
printf("%s%d: %s ethernet address %6D\n",
ifp->if_name, ifp->if_unit,
sc->le_prodname,
sc->le_ac.ac_enaddr, ":");
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_output = ether_output;
ifp->if_ioctl = le_ioctl;
ifp->if_type = IFT_ETHER;
ifp->if_addrlen = 6;
ifp->if_hdrlen = 14;
1995-05-30 08:16:23 +00:00
ether_ifattach(ifp, ETHER_BPF_SUPPORTED);
return 1;
}
static void
le_intr(
int unit)
{
int s = splimp();
le_intrs[unit]++;
(*le_intrvec[unit])(&le_softc[unit]);
splx(s);
}
#define LE_XTRA 0
static void
le_input(
le_softc_t *sc,
caddr_t seg1,
size_t total_len,
size_t len1,
caddr_t seg2)
{
struct ether_header eh;
struct mbuf *m;
if (total_len - sizeof(eh) > ETHERMTU
|| total_len - sizeof(eh) < ETHERMIN) {
sc->le_if.if_ierrors++;
return;
}
MEMCPY(&eh, seg1, sizeof(eh));
seg1 += sizeof(eh); total_len -= sizeof(eh); len1 -= sizeof(eh);
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL) {
sc->le_if.if_ierrors++;
return;
}
m->m_pkthdr.len = total_len;
m->m_pkthdr.rcvif = &sc->le_if;
if (total_len + LE_XTRA > MHLEN /* >= MINCLSIZE */) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_free(m);
sc->le_if.if_ierrors++;
return;
}
} else if (total_len + LE_XTRA > MHLEN && MINCLSIZE == (MHLEN+MLEN)) {
MGET(m->m_next, M_DONTWAIT, MT_DATA);
if (m->m_next == NULL) {
m_free(m);
sc->le_if.if_ierrors++;
return;
}
m->m_next->m_len = total_len - MHLEN - LE_XTRA;
len1 = total_len = MHLEN - LE_XTRA;
MEMCPY(mtod(m->m_next, caddr_t), &seg1[MHLEN-LE_XTRA], m->m_next->m_len);
} else if (total_len + LE_XTRA > MHLEN) {
panic("le_input: pkt of unknown length");
}
m->m_data += LE_XTRA;
m->m_len = total_len;
MEMCPY(mtod(m, caddr_t), seg1, len1);
if (seg2 != NULL)
MEMCPY(mtod(m, caddr_t) + len1, seg2, total_len - len1);
ether_input(&sc->le_if, &eh, m);
}
static int
le_ioctl(
struct ifnet *ifp,
u_long cmd,
caddr_t data)
{
le_softc_t *sc = ifp->if_softc;
int s, error = 0;
if ((sc->le_flags & IFF_UP) == 0)
return EIO;
s = splimp();
switch (cmd) {
case SIOCSIFADDR:
case SIOCGIFADDR:
case SIOCSIFMTU:
error = ether_ioctl(ifp, cmd, data);
break;
case SIOCSIFFLAGS: {
(*sc->if_init)(ifp->if_unit);
break;
}
case SIOCADDMULTI:
case SIOCDELMULTI:
/*
* Update multicast listeners
*/
(*sc->if_init)(ifp->if_unit);
error = 0;
break;
default: {
error = EINVAL;
}
}
splx(s);
return error;
}
/*
* This is the standard method of reading the DEC Address ROMS.
* I don't understand it but it does work.
*/
static int
le_read_macaddr(
le_softc_t *sc,
int ioreg,
int skippat)
{
int cksum, rom_cksum;
1995-05-30 08:16:23 +00:00
if (!skippat) {
int idx, idx2, found, octet;
static u_char testpat[] = { 0xFF, 0, 0x55, 0xAA, 0xFF, 0, 0x55, 0xAA };
idx2 = found = 0;
1995-05-30 08:16:23 +00:00
for (idx = 0; idx < 32; idx++) {
octet = LE_INB(sc, ioreg);
1995-05-30 08:16:23 +00:00
if (octet == testpat[idx2]) {
if (++idx2 == sizeof testpat) {
++found;
break;
}
} else {
idx2 = 0;
}
}
1995-05-30 08:16:23 +00:00
if (!found)
return -1;
}
cksum = 0;
sc->le_hwaddr[0] = LE_INB(sc, ioreg);
sc->le_hwaddr[1] = LE_INB(sc, ioreg);
cksum = *(u_short *) &sc->le_hwaddr[0];
sc->le_hwaddr[2] = LE_INB(sc, ioreg);
sc->le_hwaddr[3] = LE_INB(sc, ioreg);
cksum *= 2;
if (cksum > 65535) cksum -= 65535;
cksum += *(u_short *) &sc->le_hwaddr[2];
if (cksum > 65535) cksum -= 65535;
sc->le_hwaddr[4] = LE_INB(sc, ioreg);
sc->le_hwaddr[5] = LE_INB(sc, ioreg);
cksum *= 2;
if (cksum > 65535) cksum -= 65535;
cksum += *(u_short *) &sc->le_hwaddr[4];
if (cksum >= 65535) cksum -= 65535;
rom_cksum = LE_INB(sc, ioreg);
rom_cksum |= LE_INB(sc, ioreg) << 8;
1995-05-30 08:16:23 +00:00
if (cksum != rom_cksum)
return -1;
return 0;
}
static void
le_multi_filter(
le_softc_t *sc)
{
struct ifmultiaddr *ifma;
MEMSET(sc->le_mctbl, 0, (sc->le_mcmask + 1) / 8);
if (sc->le_if.if_flags & IFF_ALLMULTI) {
sc->le_flags |= IFF_MULTICAST|IFF_ALLMULTI;
return;
}
sc->le_flags &= ~IFF_MULTICAST;
/* if (interface has had an address assigned) { */
le_multi_op(sc, etherbroadcastaddr, TRUE);
sc->le_flags |= LE_BRDCSTONLY|IFF_MULTICAST;
/* } */
sc->le_flags |= IFF_MULTICAST;
for (ifma = sc->le_ac.ac_if.if_multiaddrs.lh_first; ifma;
ifma = ifma->ifma_link.le_next) {
if (ifma->ifma_addr->sa_family != AF_LINK)
continue;
le_multi_op(sc, LLADDR((struct sockaddr_dl *)ifma->ifma_addr), 1);
sc->le_flags &= ~LE_BRDCSTONLY;
}
}
static void
le_multi_op(
le_softc_t *sc,
const u_char *mca,
int enable)
{
u_int idx, bit, data, crc = 0xFFFFFFFFUL;
#ifdef __alpha
1995-05-30 08:16:23 +00:00
for (data = *(__unaligned u_long *) mca, bit = 0; bit < 48; bit++, data >>=
1)
crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LE_CRC32_POLY : 0);
#else
for (idx = 0; idx < 6; idx++)
for (data = *mca++, bit = 0; bit < 8; bit++, data >>= 1)
crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LE_CRC32_POLY : 0);
#endif
/*
* The following two line convert the N bit index into a longword index
1995-05-30 08:16:23 +00:00
* and a longword mask.
*/
crc &= sc->le_mcmask;
bit = 1 << (crc & (LE_MC_NBPW -1));
idx = crc >> (LE_MC_NBPW_LOG2);
/*
* Set or clear hash filter bit in our table.
*/
if (enable) {
sc->le_mctbl[idx] |= bit; /* Set Bit */
} else {
sc->le_mctbl[idx] &= ~bit; /* Clear Bit */
}
}
#if !defined(LE_NOLEMAC)
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
1995-05-30 08:16:23 +00:00
* Start of DEC EtherWORKS III (LEMAC) dependent code
*
*/
#define LEMAC_INTR_ENABLE(sc) \
LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) | LEMAC_IC_ALL)
#define LEMAC_INTR_DISABLE(sc) \
LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) & ~LEMAC_IC_ALL)
#define LEMAC_64K_MODE(mbase) (((mbase) >= 0x0A) && ((mbase) <= 0x0F))
#define LEMAC_32K_MODE(mbase) (((mbase) >= 0x14) && ((mbase) <= 0x1F))
#define LEMAC_2K_MODE(mbase) ( (mbase) >= 0x40)
static void lemac_init(int unit);
static void lemac_start(struct ifnet *ifp);
static void lemac_reset(IF_RESET_ARGS);
static void lemac_intr(le_softc_t *sc);
static void lemac_rne_intr(le_softc_t *sc);
static void lemac_tne_intr(le_softc_t *sc);
static void lemac_txd_intr(le_softc_t *sc, unsigned cs_value);
static void lemac_rxd_intr(le_softc_t *sc, unsigned cs_value);
static int lemac_read_eeprom(le_softc_t *sc);
static void lemac_init_adapmem(le_softc_t *sc);
#define LE_MCBITS_ALL_1S ((le_mcbits_t)~(le_mcbits_t)0)
static const le_mcbits_t lemac_allmulti_mctbl[16] = {
LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S,
LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S,
LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S,
LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S, LE_MCBITS_ALL_1S,
};
/*
* An IRQ mapping table. Less space than switch statement.
*/
static const int lemac_irqs[] = { IRQ5, IRQ10, IRQ11, IRQ15 };
/*
* Some tuning/monitoring variables.
*/
1995-12-10 13:40:44 +00:00
static unsigned lemac_deftxmax = 16; /* see lemac_max above */
static unsigned lemac_txnospc = 0; /* total # of tranmit starvations */
1995-12-10 13:40:44 +00:00
static unsigned lemac_tne_intrs = 0; /* total # of tranmit done intrs */
static unsigned lemac_rne_intrs = 0; /* total # of receive done intrs */
static unsigned lemac_txd_intrs = 0; /* total # of tranmit error intrs */
static unsigned lemac_rxd_intrs = 0; /* total # of receive error intrs */
static int
lemac_probe(
le_softc_t *sc,
const le_board_t *bd,
int *msize)
{
int irq, portval;
LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEINIT);
DELAY(LEMAC_EEP_DELAY);
/*
* Read Ethernet address if card is present.
*/
if (le_read_macaddr(sc, LEMAC_REG_APD, 0) < 0)
return 0;
MEMCPY(sc->le_ac.ac_enaddr, sc->le_hwaddr, 6);
/*
* Clear interrupts and set IRQ.
*/
portval = LE_INB(sc, LEMAC_REG_IC) & LEMAC_IC_IRQMSK;
irq = lemac_irqs[portval >> 5];
LE_OUTB(sc, LEMAC_REG_IC, portval);
/*
* Make sure settings match.
*/
if (irq != sc->le_irq) {
printf("%s%d: lemac configuration error: expected IRQ 0x%x actual 0x%x\n",
sc->le_if.if_name, sc->le_if.if_unit, sc->le_irq, irq);
return 0;
}
/*
* Try to reset the unit
*/
sc->if_init = lemac_init;
sc->le_if.if_start = lemac_start;
sc->if_reset = lemac_reset;
sc->lemac_memmode = 2;
LE_RESET(sc);
if ((sc->le_flags & IFF_UP) == 0)
return 0;
/*
* Check for correct memory base configuration.
*/
if (vtophys(sc->le_membase) != sc->lemac_membase) {
printf("%s%d: lemac configuration error: expected iomem 0x%x actual 0x%x\n",
sc->le_if.if_name, sc->le_if.if_unit,
vtophys(sc->le_membase), sc->lemac_membase);
return 0;
}
sc->le_prodname = sc->lemac_prodname;
sc->le_mctbl = sc->lemac_mctbl;
sc->le_mcmask = (1 << LEMAC_MCTBL_BITS) - 1;
sc->lemac_txmax = lemac_deftxmax;
*msize = 2048;
le_intrvec[sc->le_if.if_unit] = lemac_intr;
return LEMAC_IOSPACE;
}
1995-05-30 08:16:23 +00:00
/*
* Do a hard reset of the board;
*/
static void
lemac_reset(
IF_RESET_ARGS)
{
le_softc_t *sc = &le_softc[unit];
int portval, cksum;
/*
* Initialize board..
*/
sc->le_flags &= IFF_UP;
sc->le_if.if_flags &= ~IFF_OACTIVE;
LEMAC_INTR_DISABLE(sc);
LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEINIT);
DELAY(LEMAC_EEP_DELAY);
/* Disable Interrupts */
/* LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) & ICR_IRQ_SEL); */
/*
* Read EEPROM information. NOTE - the placement of this function
* is important because functions hereafter may rely on information
* read from the EEPROM.
*/
1995-05-30 08:16:23 +00:00
if ((cksum = lemac_read_eeprom(sc)) != LEMAC_EEP_CKSUM) {
printf("%s%d: reset: EEPROM checksum failed (0x%x)\n",
sc->le_if.if_name, sc->le_if.if_unit, cksum);
return;
}
/*
* Force to 2K mode if not already configured.
*/
portval = LE_INB(sc, LEMAC_REG_MBR);
if (!LEMAC_2K_MODE(portval)) {
if (LEMAC_64K_MODE(portval)) {
portval = (((portval * 2) & 0xF) << 4);
sc->lemac_memmode = 64;
} else if (LEMAC_32K_MODE(portval)) {
portval = ((portval & 0xF) << 4);
sc->lemac_memmode = 32;
}
LE_OUTB(sc, LEMAC_REG_MBR, portval);
}
sc->lemac_membase = portval * (2 * 1024) + (512 * 1024);
/*
* Initialize Free Memory Queue, Init mcast table with broadcast.
*/
lemac_init_adapmem(sc);
sc->le_flags |= IFF_UP;
return;
}
static void
lemac_init(
int unit)
{
le_softc_t *sc = &le_softc[unit];
int s;
if ((sc->le_flags & IFF_UP) == 0)
return;
s = splimp();
/*
* If the interface has the up flag
*/
if (sc->le_if.if_flags & IFF_UP) {
int saved_cs = LE_INB(sc, LEMAC_REG_CS);
LE_OUTB(sc, LEMAC_REG_CS, saved_cs | (LEMAC_CS_TXD | LEMAC_CS_RXD));
LE_OUTB(sc, LEMAC_REG_PA0, sc->le_ac.ac_enaddr[0]);
LE_OUTB(sc, LEMAC_REG_PA1, sc->le_ac.ac_enaddr[1]);
LE_OUTB(sc, LEMAC_REG_PA2, sc->le_ac.ac_enaddr[2]);
LE_OUTB(sc, LEMAC_REG_PA3, sc->le_ac.ac_enaddr[3]);
LE_OUTB(sc, LEMAC_REG_PA4, sc->le_ac.ac_enaddr[4]);
LE_OUTB(sc, LEMAC_REG_PA5, sc->le_ac.ac_enaddr[5]);
LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) | LEMAC_IC_IE);
if (sc->le_if.if_flags & IFF_PROMISC) {
LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_MCE | LEMAC_CS_PME);
} else {
LEMAC_INTR_DISABLE(sc);
le_multi_filter(sc);
LE_OUTB(sc, LEMAC_REG_MPN, 0);
if ((sc->le_flags | sc->le_if.if_flags) & IFF_ALLMULTI) {
MEMCPY(&sc->le_membase[LEMAC_MCTBL_OFF], lemac_allmulti_mctbl, sizeof(lemac_allmulti_mctbl));
} else {
MEMCPY(&sc->le_membase[LEMAC_MCTBL_OFF], sc->lemac_mctbl, sizeof(sc->lemac_mctbl));
}
LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_MCE);
}
LE_OUTB(sc, LEMAC_REG_CTL, LE_INB(sc, LEMAC_REG_CTL) ^ LEMAC_CTL_LED);
LEMAC_INTR_ENABLE(sc);
sc->le_if.if_flags |= IFF_RUNNING;
} else {
LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_RXD|LEMAC_CS_TXD);
LEMAC_INTR_DISABLE(sc);
sc->le_if.if_flags &= ~IFF_RUNNING;
}
splx(s);
}
/*
* What to do upon receipt of an interrupt.
*/
static void
lemac_intr(
le_softc_t *sc)
{
int cs_value;
LEMAC_INTR_DISABLE(sc); /* Mask interrupts */
/*
* Determine cause of interrupt. Receive events take
* priority over Transmit.
*/
cs_value = LE_INB(sc, LEMAC_REG_CS);
/*
* Check for Receive Queue not being empty.
* Check for Transmit Done Queue not being empty.
*/
if (cs_value & LEMAC_CS_RNE)
lemac_rne_intr(sc);
if (cs_value & LEMAC_CS_TNE)
lemac_tne_intr(sc);
/*
* Check for Transmitter Disabled.
* Check for Receiver Disabled.
*/
if (cs_value & LEMAC_CS_TXD)
lemac_txd_intr(sc, cs_value);
if (cs_value & LEMAC_CS_RXD)
lemac_rxd_intr(sc, cs_value);
/*
* Toggle LED and unmask interrupts.
*/
LE_OUTB(sc, LEMAC_REG_CTL, LE_INB(sc, LEMAC_REG_CTL) ^ LEMAC_CTL_LED);
LEMAC_INTR_ENABLE(sc); /* Unmask interrupts */
}
static void
lemac_rne_intr(
le_softc_t *sc)
{
int rxcount, rxlen, rxpg;
u_char *rxptr;
lemac_rne_intrs++;
rxcount = LE_INB(sc, LEMAC_REG_RQC);
while (rxcount--) {
rxpg = LE_INB(sc, LEMAC_REG_RQ);
LE_OUTB(sc, LEMAC_REG_MPN, rxpg);
1995-05-30 08:16:23 +00:00
rxptr = sc->le_membase;
sc->le_if.if_ipackets++;
if (*rxptr & LEMAC_RX_OK) {
/*
* Get receive length - subtract out checksum.
*/
rxlen = ((*(u_int *)rxptr >> 8) & 0x7FF) - 4;
le_input(sc, rxptr + sizeof(u_int), rxlen, rxlen, NULL);
} else { /* end if (*rxptr & LEMAC_RX_OK) */
sc->le_if.if_ierrors++;
}
LE_OUTB(sc, LEMAC_REG_FMQ, rxpg); /* Return this page to Free Memory Queue */
} /* end while (recv_count--) */
1995-05-30 08:16:23 +00:00
return;
}
static void
lemac_rxd_intr(
le_softc_t *sc,
unsigned cs_value)
{
/*
* Handle CS_RXD (Receiver disabled) here.
*
* Check Free Memory Queue Count. If not equal to zero
* then just turn Receiver back on. If it is equal to
* zero then check to see if transmitter is disabled.
* Process transmit TXD loop once more. If all else
* fails then do software init (0xC0 to EEPROM Init)
* and rebuild Free Memory Queue.
*/
lemac_rxd_intrs++;
/*
* Re-enable Receiver.
*/
cs_value &= ~LEMAC_CS_RXD;
LE_OUTB(sc, LEMAC_REG_CS, cs_value);
if (LE_INB(sc, LEMAC_REG_FMC) > 0)
return;
if (cs_value & LEMAC_CS_TXD)
lemac_txd_intr(sc, cs_value);
if ((LE_INB(sc, LEMAC_REG_CS) & LEMAC_CS_RXD) == 0)
return;
printf("%s%d: fatal RXD error, attempting recovery\n",
sc->le_if.if_name, sc->le_if.if_unit);
LE_RESET(sc);
if (sc->le_flags & IFF_UP) {
lemac_init(sc->le_if.if_unit);
return;
}
/*
* Error during initializion. Mark card as disabled.
*/
printf("%s%d: recovery failed -- board disabled\n",
sc->le_if.if_name, sc->le_if.if_unit);
return;
}
1995-05-30 08:16:23 +00:00
static void
lemac_start(
struct ifnet *ifp)
{
le_softc_t *sc = (le_softc_t *) ifp;
struct ifqueue *ifq = &ifp->if_snd;
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
LEMAC_INTR_DISABLE(sc);
while (ifq->ifq_head != NULL) {
struct mbuf *m;
int tx_pg;
u_int txhdr, txoff;
if (LE_INB(sc, LEMAC_REG_TQC) >= sc->lemac_txmax) {
ifp->if_flags |= IFF_OACTIVE;
break;
}
tx_pg = LE_INB(sc, LEMAC_REG_FMQ); /* get free memory page */
/*
* Check for good transmit page.
*/
if (tx_pg == 0 || tx_pg > sc->lemac_lastpage) {
lemac_txnospc++;
ifp->if_flags |= IFF_OACTIVE;
break;
}
IF_DEQUEUE(ifq, m);
LE_OUTB(sc, LEMAC_REG_MPN, tx_pg); /* Shift 2K window. */
/*
* The first four bytes of each transmit buffer are for
* control information. The first byte is the control
* byte, then the length (why not word aligned?), then
* the off to the buffer.
*/
txoff = (mtod(m, u_int) & (sizeof(u_long) - 1)) + LEMAC_TX_HDRSZ;
txhdr = sc->lemac_txctl | (m->m_pkthdr.len << 8) | (txoff << 24);
*(u_int *) sc->le_membase = txhdr;
/*
* Copy the packet to the board
*/
m_copydata(m, 0, m->m_pkthdr.len, sc->le_membase + txoff);
LE_OUTB(sc, LEMAC_REG_TQ, tx_pg); /* tell chip to transmit this packet */
if (sc->le_if.if_bpf)
bpf_mtap(&sc->le_if, m);
m_freem(m); /* free the mbuf */
}
LEMAC_INTR_ENABLE(sc);
}
static void
lemac_tne_intr(
le_softc_t *sc)
{
int txsts, txcount = LE_INB(sc, LEMAC_REG_TDC);
lemac_tne_intrs++;
while (txcount--) {
txsts = LE_INB(sc, LEMAC_REG_TDQ);
sc->le_if.if_opackets++; /* another one done */
if ((txsts & LEMAC_TDQ_COL) != LEMAC_TDQ_NOCOL)
sc->le_if.if_collisions++;
}
sc->le_if.if_flags &= ~IFF_OACTIVE;
lemac_start(&sc->le_if);
}
static void
lemac_txd_intr(
le_softc_t *sc,
unsigned cs_value)
{
/*
* Read transmit status, remove transmit buffer from
* transmit queue and place on free memory queue,
* then reset transmitter.
* Increment appropriate counters.
*/
lemac_txd_intrs++;
sc->le_if.if_oerrors++;
if (LE_INB(sc, LEMAC_REG_TS) & LEMAC_TS_ECL)
sc->le_if.if_collisions++;
sc->le_if.if_flags &= ~IFF_OACTIVE;
LE_OUTB(sc, LEMAC_REG_FMQ, LE_INB(sc, LEMAC_REG_TQ));
/* Get Page number and write it back out */
LE_OUTB(sc, LEMAC_REG_CS, cs_value & ~LEMAC_CS_TXD);
/* Turn back on transmitter */
return;
}
static int
lemac_read_eeprom(
le_softc_t *sc)
{
int word_off, cksum;
u_char *ep;
cksum = 0;
ep = sc->lemac_eeprom;
for (word_off = 0; word_off < LEMAC_EEP_SIZE / 2; word_off++) {
LE_OUTB(sc, LEMAC_REG_PI1, word_off);
LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEREAD);
DELAY(LEMAC_EEP_DELAY);
*ep = LE_INB(sc, LEMAC_REG_EE1); cksum += *ep++;
*ep = LE_INB(sc, LEMAC_REG_EE2); cksum += *ep++;
}
/*
* Set up Transmit Control Byte for use later during transmit.
*/
sc->lemac_txctl |= LEMAC_TX_FLAGS;
if ((sc->lemac_eeprom[LEMAC_EEP_SWFLAGS] & LEMAC_EEP_SW_SQE) == 0)
sc->lemac_txctl &= ~LEMAC_TX_SQE;
if (sc->lemac_eeprom[LEMAC_EEP_SWFLAGS] & LEMAC_EEP_SW_LAB)
sc->lemac_txctl |= LEMAC_TX_LAB;
MEMCPY(sc->lemac_prodname, &sc->lemac_eeprom[LEMAC_EEP_PRDNM], LEMAC_EEP_PRDNMSZ);
sc->lemac_prodname[LEMAC_EEP_PRDNMSZ] = '\0';
return cksum % 256;
}
static void
lemac_init_adapmem(
le_softc_t *sc)
{
int pg, conf;
conf = LE_INB(sc, LEMAC_REG_CNF);
if ((sc->lemac_eeprom[LEMAC_EEP_SETUP] & LEMAC_EEP_ST_DRAM) == 0) {
sc->lemac_lastpage = 63;
conf &= ~LEMAC_CNF_DRAM;
} else {
sc->lemac_lastpage = 127;
conf |= LEMAC_CNF_DRAM;
}
LE_OUTB(sc, LEMAC_REG_CNF, conf);
for (pg = 1; pg <= sc->lemac_lastpage; pg++)
LE_OUTB(sc, LEMAC_REG_FMQ, pg);
return;
}
#endif /* !defined(LE_NOLEMAC) */
#if !defined(LE_NOLANCE)
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Start of DEPCA (DE200/DE201/DE202/DE422 etal) support.
*
*/
static void depca_intr(le_softc_t *sc);
static int lance_init_adapmem(le_softc_t *sc);
static int lance_init_ring(le_softc_t *sc, ln_ring_t *rp, lance_ring_t *ri,
unsigned ndescs, unsigned bufoffset,
unsigned descoffset);
static void lance_init(int unit);
static void lance_reset(IF_RESET_ARGS);
static void lance_intr(le_softc_t *sc);
static int lance_rx_intr(le_softc_t *sc);
static void lance_start(struct ifnet *ifp);
static int lance_tx_intr(le_softc_t *sc);
#define LN_BUFSIZE /* 380 */ 304 /* 1520 / 4 */
#define LN_TXDESC_RATIO 2048
#define LN_DESC_MAX 128
#if LN_DOSTATS
1995-12-10 13:40:44 +00:00
static struct {
unsigned lance_rx_misses;
unsigned lance_rx_badcrc;
unsigned lance_rx_badalign;
unsigned lance_rx_badframe;
unsigned lance_rx_buferror;
unsigned lance_tx_deferred;
unsigned lance_tx_single_collisions;
unsigned lance_tx_multiple_collisions;
unsigned lance_tx_excessive_collisions;
unsigned lance_tx_late_collisions;
unsigned lance_memory_errors;
unsigned lance_inits;
unsigned lance_tx_intrs;
unsigned lance_tx_nospc[2];
unsigned lance_tx_drains[2];
unsigned lance_tx_orphaned;
unsigned lance_tx_adoptions;
unsigned lance_tx_emptied;
unsigned lance_tx_deftxint;
unsigned lance_tx_buferror;
unsigned lance_high_txoutptr;
unsigned lance_low_txheapsize;
unsigned lance_low_txfree;
unsigned lance_tx_intr_hidescs;
/* unsigned lance_tx_intr_descs[LN_DESC_MAX]; */
unsigned lance_rx_intrs;
unsigned lance_rx_badsop;
unsigned lance_rx_contig;
unsigned lance_rx_noncontig;
unsigned lance_rx_intr_hidescs;
unsigned lance_rx_ndescs[4096 / LN_BUFSIZE];
/* unsigned lance_rx_intr_descs[LN_DESC_MAX]; */
} lance_stats;
#define LN_STAT(stat) (lance_stats.lance_ ## stat)
#define LN_MINSTAT(stat, val) (LN_STAT(stat > (val)) ? LN_STAT(stat = (val)) : 0)
#define LN_MAXSTAT(stat, val) (LN_STAT(stat < (val)) ? LN_STAT(stat = (val)) : 0)
#else
#define LN_STAT(stat) 0
#define LN_MINSTAT(stat, val) 0
#define LN_MAXSTAT(stat, val) 0
#endif
#define LN_SELCSR(sc, csrno) (LE_OUTW(sc, sc->lance_rap, csrno))
#define LN_INQCSR(sc) (LE_INW(sc, sc->lance_rap))
#define LN_WRCSR(sc, val) (LE_OUTW(sc, sc->lance_rdp, val))
#define LN_RDCSR(sc) (LE_INW(sc, sc->lance_rdp))
1995-05-30 08:16:23 +00:00
#define LN_ZERO(sc, vaddr, len) bzero(vaddr, len)
#define LN_COPYTO(sc, from, to, len) bcopy(from, to, len)
#define LN_SETFLAG(sc, vaddr, val) \
(((volatile u_char *) vaddr)[3] = (val))
#define LN_PUTDESC(sc, desc, vaddr) \
(((volatile u_short *) vaddr)[0] = ((u_short *) desc)[0], \
((volatile u_short *) vaddr)[2] = ((u_short *) desc)[2], \
((volatile u_short *) vaddr)[1] = ((u_short *) desc)[1])
/*
* Only get the descriptor flags and length/status. All else
* read-only.
*/
#define LN_GETDESC(sc, desc, vaddr) \
(((u_short *) desc)[1] = ((volatile u_short *) vaddr)[1], \
((u_short *) desc)[3] = ((volatile u_short *) vaddr)[3])
/*
* These definitions are specific to the DEC "DEPCA-style" NICs.
* (DEPCA, DE10x, DE20[012], DE422)
*
*/
#define DEPCA_REG_NICSR 0 /* (RW;16) NI Control / Status */
#define DEPCA_REG_RDP 4 /* (RW:16) LANCE RDP (data) register */
#define DEPCA_REG_RAP 6 /* (RW:16) LANCE RAP (address) register */
#define DEPCA_REG_ADDRROM 12 /* (R : 8) DEPCA Ethernet Address ROM */
#define DEPCA_IOSPACE 16 /* DEPCAs use 16 bytes of IO space */
#define DEPCA_NICSR_LED 0x0001 /* Light the LED on the back of the DEPCA */
#define DEPCA_NICSR_ENABINTR 0x0002 /* Enable Interrupts */
#define DEPCA_NICSR_MASKINTR 0x0004 /* Mask Interrupts */
#define DEPCA_NICSR_AAC 0x0008 /* Address Counter Clear */
#define DEPCA_NICSR_REMOTEBOOT 0x0010 /* Remote Boot Enabled (ignored) */
#define DEPCA_NICSR_32KRAM 0x0020 /* DEPCA LANCE RAM size 64K (C) / 32K (S) */
#define DEPCA_NICSR_LOW32K 0x0040 /* Bank Select (A15 = !This Bit) */
#define DEPCA_NICSR_SHE 0x0080 /* Shared RAM Enabled (ie hide ROM) */
#define DEPCA_NICSR_BOOTTMO 0x0100 /* Remote Boot Timeout (ignored) */
#define DEPCA_RDNICSR(sc) (LE_INW(sc, DEPCA_REG_NICSR))
#define DEPCA_WRNICSR(sc, val) (LE_OUTW(sc, DEPCA_REG_NICSR, val))
#define DEPCA_IDSTR_OFFSET 0xC006 /* ID String Offset */
#define DEPCA_REG_EISAID 0x80
#define DEPCA_EISAID_MASK 0xf0ffffff
#define DEPCA_EISAID_DE422 0x2042A310
typedef enum {
DEPCA_CLASSIC,
DEPCA_DE100, DEPCA_DE101,
DEPCA_EE100,
DEPCA_DE200, DEPCA_DE201, DEPCA_DE202,
DEPCA_DE422,
DEPCA_UNKNOWN
} depca_t;
1995-05-30 08:16:23 +00:00
static const char *depca_signatures[] = {
"DEPCA",
"DE100", "DE101",
"EE100",
"DE200", "DE201", "DE202",
"DE422",
NULL
};
static int
depca_probe(
le_softc_t *sc,
const le_board_t *bd,
int *msize)
{
unsigned nicsr, idx, idstr_offset = DEPCA_IDSTR_OFFSET;
/*
* Find out how memory we are dealing with. Adjust
* the ID string offset approriately if we are at
* 32K. Make sure the ROM is enabled.
*/
nicsr = DEPCA_RDNICSR(sc);
nicsr &= ~(DEPCA_NICSR_SHE|DEPCA_NICSR_LED|DEPCA_NICSR_ENABINTR);
1995-05-30 08:16:23 +00:00
if (nicsr & DEPCA_NICSR_32KRAM) {
/*
* Make we are going to read the upper
* 32K so we do read the ROM.
*/
sc->lance_ramsize = 32 * 1024;
nicsr &= ~DEPCA_NICSR_LOW32K;
sc->lance_ramoffset = 32 * 1024;
idstr_offset -= sc->lance_ramsize;
} else {
sc->lance_ramsize = 64 * 1024;
sc->lance_ramoffset = 0;
}
DEPCA_WRNICSR(sc, nicsr);
sc->le_prodname = NULL;
for (idx = 0; depca_signatures[idx] != NULL; idx++) {
if (bcmp(depca_signatures[idx], sc->le_membase + idstr_offset, 5) == 0) {
sc->le_prodname = depca_signatures[idx];
break;
}
}
if (sc->le_prodname == NULL) {
/*
* Try to get the EISA device if it's a DE422.
*/
if (sc->le_iobase > 0x1000 && (sc->le_iobase & 0x0F00) == 0x0C00
&& (LE_INL(sc, DEPCA_REG_EISAID) & DEPCA_EISAID_MASK)
== DEPCA_EISAID_DE422) {
sc->le_prodname = "DE422";
} else {
return 0;
}
}
if (idx == DEPCA_CLASSIC)
sc->lance_ramsize -= 16384; /* Can't use the ROM area on a DEPCA */
/*
* Try to read the address ROM.
* Stop the LANCE, reset the Address ROM Counter (AAC),
* read the NICSR to "clock" in the reset, and then
* re-enable the Address ROM Counter. Now read the
* address ROM.
*/
sc->lance_rdp = DEPCA_REG_RDP;
sc->lance_rap = DEPCA_REG_RAP;
sc->lance_csr3 = LN_CSR3_ALE;
sc->le_mctbl = sc->lance_initb.ln_multi_mask;
sc->le_mcmask = LN_MC_MASK;
LN_SELCSR(sc, LN_CSR0);
LN_WRCSR(sc, LN_CSR0_STOP);
if (idx < DEPCA_DE200) {
DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) & ~DEPCA_NICSR_AAC);
DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) | DEPCA_NICSR_AAC);
}
if (le_read_macaddr(sc, DEPCA_REG_ADDRROM, idx == DEPCA_CLASSIC) < 0)
return 0;
MEMCPY(sc->le_ac.ac_enaddr, sc->le_hwaddr, 6);
/*
* Renable shared RAM.
*/
DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) | DEPCA_NICSR_SHE);
le_intrvec[sc->le_if.if_unit] = depca_intr;
if (!lance_init_adapmem(sc))
return 0;
sc->if_reset = lance_reset;
sc->if_init = lance_init;
sc->le_if.if_start = lance_start;
DEPCA_WRNICSR(sc, DEPCA_NICSR_SHE | DEPCA_NICSR_ENABINTR);
LE_RESET(sc);
LN_STAT(low_txfree = sc->lance_txinfo.ri_max);
LN_STAT(low_txheapsize = 0xFFFFFFFF);
*msize = sc->lance_ramsize;
return DEPCA_IOSPACE;
}
static void
depca_intr(
le_softc_t *sc)
{
DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) ^ DEPCA_NICSR_LED);
lance_intr(sc);
}
/*
1995-05-30 08:16:23 +00:00
* Here's as good a place to describe our paritioning of the
* LANCE shared RAM space. (NOTE: this driver does not yet support
* the concept of a LANCE being able to DMA).
*
* First is the 24 (00:23) bytes for LANCE Initialization Block
* Next are the recieve descriptors. The number is calculated from
* how many LN_BUFSIZE buffers we can allocate (this number must
* be a power of 2). Next are the transmit descriptors. The amount
* of transmit descriptors is derived from the size of the RAM
* divided by 1K. Now come the receive buffers (one for each receive
* descriptor). Finally is the transmit heap. (no fixed buffers are
* allocated so as to make the most use of the limited space).
*/
static int
lance_init_adapmem(
le_softc_t *sc)
{
lance_addr_t rxbufoffset;
lance_addr_t rxdescoffset, txdescoffset;
unsigned rxdescs, txdescs;
/*
* First calculate how many descriptors we heap.
* Note this assumes the ramsize is a power of two.
*/
sc->lance_rxbufsize = LN_BUFSIZE;
rxdescs = 1;
while (rxdescs * sc->lance_rxbufsize < sc->lance_ramsize)
rxdescs *= 2;
rxdescs /= 2;
if (rxdescs > LN_DESC_MAX) {
sc->lance_rxbufsize *= rxdescs / LN_DESC_MAX;
rxdescs = LN_DESC_MAX;
}
txdescs = sc->lance_ramsize / LN_TXDESC_RATIO;
if (txdescs > LN_DESC_MAX)
txdescs = LN_DESC_MAX;
/*
* Now calculate where everything goes in memory
*/
rxdescoffset = sizeof(ln_initb_t);
txdescoffset = rxdescoffset + sizeof(ln_desc_t) * rxdescs;
rxbufoffset = txdescoffset + sizeof(ln_desc_t) * txdescs;
sc->le_mctbl = (le_mcbits_t *) sc->lance_initb.ln_multi_mask;
/*
* Remember these for debugging.
*/
sc->lance_raminitb = (ln_initb_t *) sc->le_membase;
sc->lance_ramdesc = (ln_desc_t *) (sc->le_membase + rxdescoffset);
/*
* Initialize the rings.
*/
if (!lance_init_ring(sc, &sc->lance_initb.ln_rxring, &sc->lance_rxinfo,
rxdescs, rxbufoffset, rxdescoffset))
return 0;
sc->lance_rxinfo.ri_heap = rxbufoffset;
sc->lance_rxinfo.ri_heapend = rxbufoffset + sc->lance_rxbufsize * rxdescs;
if (!lance_init_ring(sc, &sc->lance_initb.ln_txring, &sc->lance_txinfo,
txdescs, 0, txdescoffset))
return 0;
sc->lance_txinfo.ri_heap = sc->lance_rxinfo.ri_heapend;
sc->lance_txinfo.ri_heapend = sc->lance_ramsize;
/*
* Set CSR1 and CSR2 to the address of the init block (which
* for us is always 0.
*/
sc->lance_csr1 = LN_ADDR_LO(0 + sc->lance_ramoffset);
sc->lance_csr2 = LN_ADDR_HI(0 + sc->lance_ramoffset);
return 1;
}
static int
lance_init_ring(
le_softc_t *sc,
ln_ring_t *rp,
lance_ring_t *ri,
unsigned ndescs,
lance_addr_t bufoffset,
lance_addr_t descoffset)
{
lance_descinfo_t *di;
/*
* Initialize the ring pointer in the LANCE InitBlock
*/
rp->r_addr_lo = LN_ADDR_LO(descoffset + sc->lance_ramoffset);
rp->r_addr_hi = LN_ADDR_HI(descoffset + sc->lance_ramoffset);
rp->r_log2_size = ffs(ndescs) - 1;
/*
* Allocate the ring entry descriptors and initialize
* our ring information data structure. All these are
* our copies and do not live in the LANCE RAM.
*/
ri->ri_first = (lance_descinfo_t *) malloc(ndescs * sizeof(*di), M_DEVBUF, M_NOWAIT);
if (ri->ri_first == NULL) {
printf("lance_init_ring: malloc(%d) failed\n", ndescs * sizeof(*di));
return 0;
}
ri->ri_free = ri->ri_max = ndescs;
ri->ri_last = ri->ri_first + ri->ri_max;
for (di = ri->ri_first; di < ri->ri_last; di++) {
di->di_addr = sc->le_membase + descoffset;
di->di_mbuf = NULL;
if (bufoffset) {
di->di_bufaddr = bufoffset;
di->di_buflen = sc->lance_rxbufsize;
bufoffset += sc->lance_rxbufsize;
}
descoffset += sizeof(ln_desc_t);
}
return 1;
}
static void
lance_dumpcsrs(
le_softc_t *sc,
const char *id)
{
printf("%s%d: %s: nicsr=%04x",
sc->le_if.if_name, sc->le_if.if_unit,
id, DEPCA_RDNICSR(sc));
LN_SELCSR(sc, LN_CSR0); printf(" csr0=%04x", LN_RDCSR(sc));
LN_SELCSR(sc, LN_CSR1); printf(" csr1=%04x", LN_RDCSR(sc));
LN_SELCSR(sc, LN_CSR2); printf(" csr2=%04x", LN_RDCSR(sc));
LN_SELCSR(sc, LN_CSR3); printf(" csr3=%04x\n", LN_RDCSR(sc));
LN_SELCSR(sc, LN_CSR0);
}
static void
lance_reset(
IF_RESET_ARGS)
{
le_softc_t *sc = &le_softc[unit];
register int cnt, csr;
/* lance_dumpcsrs(sc, "lance_reset: start"); */
LN_WRCSR(sc, LN_RDCSR(sc) & ~LN_CSR0_ENABINTR);
LN_WRCSR(sc, LN_CSR0_STOP);
DELAY(100);
sc->le_flags &= ~IFF_UP;
sc->le_if.if_flags &= ~(IFF_UP|IFF_RUNNING);
le_multi_filter(sc); /* initialize the multicast table */
if ((sc->le_flags | sc->le_if.if_flags) & IFF_ALLMULTI) {
sc->lance_initb.ln_multi_mask[0] = 0xFFFFU;
sc->lance_initb.ln_multi_mask[1] = 0xFFFFU;
sc->lance_initb.ln_multi_mask[2] = 0xFFFFU;
sc->lance_initb.ln_multi_mask[3] = 0xFFFFU;
}
sc->lance_initb.ln_physaddr[0] = ((u_short *) sc->le_ac.ac_enaddr)[0];
sc->lance_initb.ln_physaddr[1] = ((u_short *) sc->le_ac.ac_enaddr)[1];
sc->lance_initb.ln_physaddr[2] = ((u_short *) sc->le_ac.ac_enaddr)[2];
if (sc->le_if.if_flags & IFF_PROMISC) {
sc->lance_initb.ln_mode |= LN_MODE_PROMISC;
} else {
sc->lance_initb.ln_mode &= ~LN_MODE_PROMISC;
}
/*
* We force the init block to be at the start
* of the LANCE's RAM buffer.
*/
LN_COPYTO(sc, &sc->lance_initb, sc->le_membase, sizeof(sc->lance_initb));
LN_SELCSR(sc, LN_CSR1); LN_WRCSR(sc, sc->lance_csr1);
LN_SELCSR(sc, LN_CSR2); LN_WRCSR(sc, sc->lance_csr2);
LN_SELCSR(sc, LN_CSR3); LN_WRCSR(sc, sc->lance_csr3);
/* lance_dumpcsrs(sc, "lance_reset: preinit"); */
/*
* clear INITDONE and INIT the chip
*/
LN_SELCSR(sc, LN_CSR0);
LN_WRCSR(sc, LN_CSR0_INIT|LN_CSR0_INITDONE);
csr = 0;
cnt = 100;
while (cnt-- > 0) {
if (((csr = LN_RDCSR(sc)) & LN_CSR0_INITDONE) != 0)
break;
DELAY(10000);
}
if ((csr & LN_CSR0_INITDONE) == 0) { /* make sure we got out okay */
lance_dumpcsrs(sc, "lance_reset: reset failure");
} else {
/* lance_dumpcsrs(sc, "lance_reset: end"); */
sc->le_if.if_flags |= IFF_UP;
sc->le_flags |= IFF_UP;
}
}
static void
lance_init(
int unit)
{
le_softc_t *sc = &le_softc[unit];
lance_ring_t *ri;
lance_descinfo_t *di;
ln_desc_t desc;
LN_STAT(inits++);
if (sc->le_if.if_flags & IFF_RUNNING) {
LE_RESET(sc);
lance_tx_intr(sc);
/*
* If we were running, requeue any pending transmits.
*/
ri = &sc->lance_txinfo;
di = ri->ri_nextout;
while (ri->ri_free < ri->ri_max) {
if (--di == ri->ri_first)
di = ri->ri_nextout - 1;
if (di->di_mbuf == NULL)
break;
IF_PREPEND(&sc->le_if.if_snd, di->di_mbuf);
di->di_mbuf = NULL;
ri->ri_free++;
}
} else {
LE_RESET(sc);
}
/*
* Reset the transmit ring. Make sure we own all the buffers.
* Also reset the transmit heap.
*/
sc->le_if.if_flags &= ~IFF_OACTIVE;
ri = &sc->lance_txinfo;
for (di = ri->ri_first; di < ri->ri_last; di++) {
if (di->di_mbuf != NULL) {
m_freem(di->di_mbuf);
di->di_mbuf = NULL;
}
desc.d_flag = 0;
desc.d_addr_lo = LN_ADDR_LO(ri->ri_heap + sc->lance_ramoffset);
desc.d_addr_hi = LN_ADDR_HI(ri->ri_heap + sc->lance_ramoffset);
desc.d_buflen = 0;
LN_PUTDESC(sc, &desc, di->di_addr);
}
ri->ri_nextin = ri->ri_nextout = ri->ri_first;
ri->ri_free = ri->ri_max;
ri->ri_outptr = ri->ri_heap;
ri->ri_outsize = ri->ri_heapend - ri->ri_heap;
ri = &sc->lance_rxinfo;
desc.d_flag = LN_DFLAG_OWNER;
desc.d_buflen = 0 - sc->lance_rxbufsize;
for (di = ri->ri_first; di < ri->ri_last; di++) {
desc.d_addr_lo = LN_ADDR_LO(di->di_bufaddr + sc->lance_ramoffset);
desc.d_addr_hi = LN_ADDR_HI(di->di_bufaddr + sc->lance_ramoffset);
LN_PUTDESC(sc, &desc, di->di_addr);
}
ri->ri_nextin = ri->ri_nextout = ri->ri_first;
ri->ri_outptr = ri->ri_heap;
ri->ri_outsize = ri->ri_heapend - ri->ri_heap;
ri->ri_free = 0;
if (sc->le_if.if_flags & IFF_UP) {
sc->le_if.if_flags |= IFF_RUNNING;
LN_WRCSR(sc, LN_CSR0_START|LN_CSR0_INITDONE|LN_CSR0_ENABINTR);
/* lance_dumpcsrs(sc, "lance_init: up"); */
lance_start(&sc->le_if);
} else {
/* lance_dumpcsrs(sc, "lance_init: down"); */
sc->le_if.if_flags &= ~IFF_RUNNING;
}
}
static void
lance_intr(
le_softc_t *sc)
{
unsigned oldcsr;
oldcsr = LN_RDCSR(sc);
oldcsr &= ~LN_CSR0_ENABINTR;
LN_WRCSR(sc, oldcsr);
LN_WRCSR(sc, LN_CSR0_ENABINTR);
if (oldcsr & LN_CSR0_ERRSUM) {
if (oldcsr & LN_CSR0_MISS) {
/*
* LN_CSR0_MISS is signaled when the LANCE receiver
* loses a packet because it doesn't own a receive
* descriptor. Rev. D LANCE chips, which are no
* longer used, require a chip reset as described
* below.
*/
LN_STAT(rx_misses++);
}
if (oldcsr & LN_CSR0_MEMERROR) {
LN_STAT(memory_errors++);
if (oldcsr & (LN_CSR0_RXON|LN_CSR0_TXON)) {
lance_init(sc->le_if.if_unit);
return;
}
}
}
if ((oldcsr & LN_CSR0_RXINT) && lance_rx_intr(sc)) {
lance_init(sc->le_if.if_unit);
return;
}
if (oldcsr & LN_CSR0_TXINT) {
if (lance_tx_intr(sc))
lance_start(&sc->le_if);
}
if (oldcsr == (LN_CSR0_PENDINTR|LN_CSR0_RXON|LN_CSR0_TXON))
printf("%s%d: lance_intr: stray interrupt\n",
sc->le_if.if_name, sc->le_if.if_unit);
}
static int
lance_rx_intr(
le_softc_t *sc)
{
lance_ring_t *ri = &sc->lance_rxinfo;
lance_descinfo_t *eop;
ln_desc_t desc;
int ndescs, total_len, rxdescs;
LN_STAT(rx_intrs++);
for (rxdescs = 0;;) {
/*
* Now to try to find the end of this packet chain.
*/
for (ndescs = 1, eop = ri->ri_nextin;; ndescs++) {
/*
* If we don't own this descriptor, the packet ain't
* all here so return because we are done.
*/
LN_GETDESC(sc, &desc, eop->di_addr);
if (desc.d_flag & LN_DFLAG_OWNER)
return 0;
/*
* In case we have missed a packet and gotten the
* LANCE confused, make sure we are pointing at the
* start of a packet. If we aren't, something is really
* strange so reinit the LANCE.
*/
if (desc.d_flag & LN_DFLAG_RxBUFERROR) {
LN_STAT(rx_buferror++);
return 1;
}
if ((desc.d_flag & LN_DFLAG_SOP) && eop != ri->ri_nextin) {
LN_STAT(rx_badsop++);
return 1;
}
if (desc.d_flag & LN_DFLAG_EOP)
break;
if (++eop == ri->ri_last)
eop = ri->ri_first;
}
total_len = (desc.d_status & LN_DSTS_RxLENMASK) - 4;
if ((desc.d_flag & LN_DFLAG_RxERRSUM) == 0) {
/*
* Valid Packet -- If the SOP is less than or equal to the EOP
* or the length is less than the receive buffer size, then the
* packet is contiguous in memory and can be copied in one shot.
* Otherwise we need to copy two segments to get the entire
* packet.
*/
if (ri->ri_nextin <= eop || total_len <= ri->ri_heapend - ri->ri_nextin->di_bufaddr) {
le_input(sc, sc->le_membase + ri->ri_nextin->di_bufaddr,
total_len, total_len, NULL);
LN_STAT(rx_contig++);
} else {
le_input(sc, sc->le_membase + ri->ri_nextin->di_bufaddr,
total_len,
ri->ri_heapend - ri->ri_nextin->di_bufaddr,
sc->le_membase + ri->ri_first->di_bufaddr);
LN_STAT(rx_noncontig++);
}
} else {
/*
* If the packet is bad, increment the
1995-05-30 08:16:23 +00:00
* counters.
*/
sc->le_if.if_ierrors++;
if (desc.d_flag & LN_DFLAG_RxBADCRC)
LN_STAT(rx_badcrc++);
if (desc.d_flag & LN_DFLAG_RxOVERFLOW)
LN_STAT(rx_badalign++);
if (desc.d_flag & LN_DFLAG_RxFRAMING)
LN_STAT(rx_badframe++);
}
sc->le_if.if_ipackets++;
LN_STAT(rx_ndescs[ndescs-1]++);
rxdescs += ndescs;
while (ndescs-- > 0) {
LN_SETFLAG(sc, ri->ri_nextin->di_addr, LN_DFLAG_OWNER);
if (++ri->ri_nextin == ri->ri_last)
ri->ri_nextin = ri->ri_first;
}
}
/* LN_STAT(rx_intr_descs[rxdescs]++); */
LN_MAXSTAT(rx_intr_hidescs, rxdescs);
return 0;
}
static void
lance_start(
struct ifnet *ifp)
{
le_softc_t *sc = (le_softc_t *) ifp;
struct ifqueue *ifq = &ifp->if_snd;
lance_ring_t *ri = &sc->lance_txinfo;
lance_descinfo_t *di;
ln_desc_t desc;
unsigned len, slop;
struct mbuf *m, *m0;
caddr_t bp;
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
for (;;) {
IF_DEQUEUE(ifq, m);
if (m == NULL)
break;
1995-05-30 08:16:23 +00:00
/*
* Make the packet meets the minimum size for Ethernet.
* The slop is so that we also use an even number of longwards.
*/
len = ETHERMIN + sizeof(struct ether_header);
if (m->m_pkthdr.len > len)
len = m->m_pkthdr.len;
slop = (8 - len) & 3;
/*
* If there are no free ring entries (there must be always
* one owned by the host), or there's not enough space for
* this packet, or this packet would wrap around the end
* of LANCE RAM then wait for the transmits to empty for
* space and ring entries to become available.
*/
if (ri->ri_free == 1 || len + slop > ri->ri_outsize) {
/*
* Try to see if we can free up anything off the transit ring.
*/
if (lance_tx_intr(sc) > 0) {
LN_STAT(tx_drains[0]++);
IF_PREPEND(ifq, m);
continue;
}
LN_STAT(tx_nospc[0]++);
break;
}
1995-05-30 08:16:23 +00:00
if (len + slop > ri->ri_heapend - ri->ri_outptr) {
/*
* Since the packet won't fit in the end of the transmit
* heap, see if there is space at the beginning of the transmit
* heap. If not, try again when there is space.
*/
LN_STAT(tx_orphaned++);
slop += ri->ri_heapend - ri->ri_outptr;
if (len + slop > ri->ri_outsize) {
LN_STAT(tx_nospc[1]++);
break;
}
/*
* Point to the beginning of the heap
*/
ri->ri_outptr = ri->ri_heap;
LN_STAT(tx_adoptions++);
}
1995-05-30 08:16:23 +00:00
/*
* Initialize the descriptor (saving the buffer address,
* buffer length, and mbuf) and write the packet out
* to the board.
*/
di = ri->ri_nextout;
di->di_bufaddr = ri->ri_outptr;
di->di_buflen = len + slop;
di->di_mbuf = m;
bp = sc->le_membase + di->di_bufaddr;
for (m0 = m; m0 != NULL; m0 = m0->m_next) {
LN_COPYTO(sc, mtod(m0, caddr_t), bp, m0->m_len);
bp += m0->m_len;
}
/*
* Zero out the remainder if needed (< ETHERMIN).
*/
if (m->m_pkthdr.len < len)
LN_ZERO(sc, bp, len - m->m_pkthdr.len);
/*
* Finally, copy out the descriptor and tell the
* LANCE to transmit!.
*/
desc.d_buflen = 0 - len;
desc.d_addr_lo = LN_ADDR_LO(di->di_bufaddr + sc->lance_ramoffset);
desc.d_addr_hi = LN_ADDR_HI(di->di_bufaddr + sc->lance_ramoffset);
desc.d_flag = LN_DFLAG_SOP|LN_DFLAG_EOP|LN_DFLAG_OWNER;
LN_PUTDESC(sc, &desc, di->di_addr);
LN_WRCSR(sc, LN_CSR0_TXDEMAND|LN_CSR0_ENABINTR);
/*
* Do our bookkeeping with our transmit heap.
* (if we wrap, point back to the beginning).
*/
ri->ri_outptr += di->di_buflen;
ri->ri_outsize -= di->di_buflen;
LN_MAXSTAT(high_txoutptr, ri->ri_outptr);
LN_MINSTAT(low_txheapsize, ri->ri_outsize);
if (ri->ri_outptr == ri->ri_heapend)
ri->ri_outptr = ri->ri_heap;
ri->ri_free--;
if (++ri->ri_nextout == ri->ri_last)
ri->ri_nextout = ri->ri_first;
LN_MINSTAT(low_txfree, ri->ri_free);
}
if (m != NULL) {
ifp->if_flags |= IFF_OACTIVE;
IF_PREPEND(ifq, m);
}
}
static int
lance_tx_intr(
le_softc_t *sc)
{
lance_ring_t *ri = &sc->lance_txinfo;
unsigned xmits;
LN_STAT(tx_intrs++);
for (xmits = 0; ri->ri_free < ri->ri_max; ) {
ln_desc_t desc;
LN_GETDESC(sc, &desc, ri->ri_nextin->di_addr);
1995-05-30 08:16:23 +00:00
if (desc.d_flag & LN_DFLAG_OWNER)
break;
if (desc.d_flag & (LN_DFLAG_TxONECOLL|LN_DFLAG_TxMULTCOLL))
sc->le_if.if_collisions++;
if (desc.d_flag & LN_DFLAG_TxDEFERRED)
LN_STAT(tx_deferred++);
if (desc.d_flag & LN_DFLAG_TxONECOLL)
LN_STAT(tx_single_collisions++);
if (desc.d_flag & LN_DFLAG_TxMULTCOLL)
LN_STAT(tx_multiple_collisions++);
if (desc.d_flag & LN_DFLAG_TxERRSUM) {
if (desc.d_status & (LN_DSTS_TxUNDERFLOW|LN_DSTS_TxBUFERROR|
LN_DSTS_TxEXCCOLL|LN_DSTS_TxLATECOLL)) {
if (desc.d_status & LN_DSTS_TxEXCCOLL) {
unsigned tdr;
LN_STAT(tx_excessive_collisions++);
if ((tdr = (desc.d_status & LN_DSTS_TxTDRMASK)) > 0) {
tdr *= 100;
printf("%s%d: lance: warning: excessive collisions: TDR %dns (%d-%dm)\n",
sc->le_if.if_name, sc->le_if.if_unit,
tdr, (tdr*99)/1000, (tdr*117)/1000);
}
}
if (desc.d_status & LN_DSTS_TxBUFERROR)
LN_STAT(tx_buferror++);
sc->le_if.if_oerrors++;
if ((desc.d_status & LN_DSTS_TxLATECOLL) == 0) {
lance_init(sc->le_if.if_unit);
return 0;
} else {
LN_STAT(tx_late_collisions++);
}
}
}
m_freem(ri->ri_nextin->di_mbuf);
ri->ri_nextin->di_mbuf = NULL;
sc->le_if.if_opackets++;
ri->ri_free++;
ri->ri_outsize += ri->ri_nextin->di_buflen;
if (++ri->ri_nextin == ri->ri_last)
ri->ri_nextin = ri->ri_first;
sc->le_if.if_flags &= ~IFF_OACTIVE;
xmits++;
}
if (ri->ri_free == ri->ri_max)
LN_STAT(tx_emptied++);
/* LN_STAT(tx_intr_descs[xmits]++); */
LN_MAXSTAT(tx_intr_hidescs, xmits);
return xmits;
}
#endif /* !defined(LE_NOLANCE) */