NetBSD/sys/dev/pci/if_lmc.c
martin 99772f59c4 Move net/if_sppp.h to net/if_spppvar.h, create a new net/if_sppp.h
containing the userland visible thinks (i.e. ioctl definitions).

Remove all (both) old ioctls, as they had a brain dead API and made keeping
binary compatibility more or less impossible.

Replace by several new ioctls. While there, remove any arbitrary limits
(resulting from the old, broken ioctls) and allow any length of names
and passwords.
2002-01-04 12:21:24 +00:00

1710 lines
44 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* $NetBSD: if_lmc.c,v 1.18 2002/01/04 12:21:24 martin Exp $ */
/*-
* Copyright (c) 1997-1999 LAN Media Corporation (LMC)
* All rights reserved. www.lanmedia.com
*
* This code is written by Michael Graff <graff@vix.com> for LMC.
* The code is derived from permitted modifications to software created
* by Matt Thomas (matt@3am-software.com).
*
* 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 marketing or advertising materials mentioning features or
* use of this software must display the following acknowledgement:
* This product includes software developed by LAN Media Corporation
* and its contributors.
* 4. Neither the name of LAN Media Corporation 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 LAN MEDIA CORPORATION 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 FOUNDATION 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.
*/
/*-
* Copyright (c) 1994-1997 Matt Thomas (matt@3am-software.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 without 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.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_lmc.c,v 1.18 2002/01/04 12:21:24 martin Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/proc.h> /* only for declaration of wakeup() used by vm.h */
#if defined(__FreeBSD__)
#include <machine/clock.h>
#elif defined(__bsdi__) || defined(__NetBSD__)
#include <sys/device.h>
#endif
#if defined(__NetBSD__)
#include <dev/pci/pcidevs.h>
#include "rnd.h"
#if NRND > 0
#include <sys/rnd.h>
#endif
#endif
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/netisr.h>
#include "bpfilter.h"
#if NBPFILTER > 0
#include <net/bpf.h>
#include <net/bpfdesc.h>
#endif
#if defined(__FreeBSD__)
#include <net/if_sppp.h>
#elif defined(__NetBSD__)
#include <net/if_spppvar.h>
#endif
#if defined(__bsdi__)
#if INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#endif
#include <net/netisr.h>
#include <net/if.h>
#include <net/netisr.h>
#include <net/if_types.h>
#include <net/if_p2p.h>
#include <net/if_c_hdlc.h>
#endif
#if defined(__NetBSD__)
#include <uvm/uvm_extern.h>
#endif
#if defined(__FreeBSD__)
#include <vm/vm.h>
#include <vm/pmap.h>
#include <pci.h>
#if NPCI > 0
#include <pci/pcivar.h>
#include <pci/dc21040reg.h>
#endif
#endif /* __FreeBSD__ */
#if defined(__bsdi__)
#include <vm/vm.h>
#include <i386/pci/ic/dc21040.h>
#include <i386/isa/isa.h>
#include <i386/isa/icu.h>
#include <i386/isa/dma.h>
#include <i386/isa/isavar.h>
#include <i386/pci/pci.h>
#endif /* __bsdi__ */
#if defined(__NetBSD__)
#include <machine/bus.h>
#if defined(__alpha__)
#include <machine/intr.h>
#endif
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/ic/dc21040reg.h>
#endif /* __NetBSD__ */
/*
* Sigh. Every OS puts these in different places.
*/
#if defined(__NetBSD__)
#include <dev/pci/if_lmc_types.h>
#include <dev/pci/if_lmcioctl.h>
#include <dev/pci/if_lmcvar.h>
#elif defined(__FreeBSD__)
#include "pci/if_lmc_types.h"
#include "pci/if_lmcioctl.h"
#include "pci/if_lmcvar.h"
#else /* BSDI */
#include "i386/pci/if_lmctypes.h"
#include "i386/pci/if_lmcioctl.h"
#include "i386/pci/if_lmcvar.h"
#endif
/*
* This module supports
* the DEC 21140A pass 2.2 PCI Fast Ethernet Controller.
*/
static ifnet_ret_t lmc_ifstart_one(struct ifnet *ifp);
static ifnet_ret_t lmc_ifstart(struct ifnet *ifp);
static struct mbuf *lmc_txput(lmc_softc_t * const sc, struct mbuf *m);
static void lmc_rx_intr(lmc_softc_t * const sc);
#if defined(__NetBSD__) || defined(__FreeBSD__)
static void lmc_watchdog(struct ifnet *ifp);
#endif
#if defined(__bsdi__)
static int lmc_watchdog(int);
#endif
static void lmc_ifup(lmc_softc_t * const sc);
static void lmc_ifdown(lmc_softc_t * const sc);
/*
* Code the read the SROM and MII bit streams (I2C)
*/
static inline void
lmc_delay_300ns(lmc_softc_t * const sc)
{
int idx;
for (idx = (300 / 33) + 1; idx > 0; idx--)
(void)LMC_CSR_READ(sc, csr_busmode);
}
#define EMIT \
do { \
LMC_CSR_WRITE(sc, csr_srom_mii, csr); \
lmc_delay_300ns(sc); \
} while (0)
static inline void
lmc_srom_idle(lmc_softc_t * const sc)
{
unsigned bit, csr;
csr = SROMSEL ; EMIT;
csr = SROMSEL | SROMRD; EMIT;
csr ^= SROMCS; EMIT;
csr ^= SROMCLKON; EMIT;
/*
* Write 25 cycles of 0 which will force the SROM to be idle.
*/
for (bit = 3 + SROM_BITWIDTH + 16; bit > 0; bit--) {
csr ^= SROMCLKOFF; EMIT; /* clock low; data not valid */
csr ^= SROMCLKON; EMIT; /* clock high; data valid */
}
csr ^= SROMCLKOFF; EMIT;
csr ^= SROMCS; EMIT;
csr = 0; EMIT;
}
static void
lmc_srom_read(lmc_softc_t * const sc)
{
unsigned idx;
const unsigned bitwidth = SROM_BITWIDTH;
const unsigned cmdmask = (SROMCMD_RD << bitwidth);
const unsigned msb = 1 << (bitwidth + 3 - 1);
unsigned lastidx = (1 << bitwidth) - 1;
lmc_srom_idle(sc);
for (idx = 0; idx <= lastidx; idx++) {
unsigned lastbit, data, bits, bit, csr;
csr = SROMSEL ; EMIT;
csr = SROMSEL | SROMRD; EMIT;
csr ^= SROMCSON; EMIT;
csr ^= SROMCLKON; EMIT;
lastbit = 0;
for (bits = idx|cmdmask, bit = bitwidth + 3
; bit > 0
; bit--, bits <<= 1) {
const unsigned thisbit = bits & msb;
csr ^= SROMCLKOFF; EMIT; /* clock L data invalid */
if (thisbit != lastbit) {
csr ^= SROMDOUT; EMIT;/* clock L invert data */
} else {
EMIT;
}
csr ^= SROMCLKON; EMIT; /* clock H data valid */
lastbit = thisbit;
}
csr ^= SROMCLKOFF; EMIT;
for (data = 0, bits = 0; bits < 16; bits++) {
data <<= 1;
csr ^= SROMCLKON; EMIT; /* clock H data valid */
data |= LMC_CSR_READ(sc, csr_srom_mii) & SROMDIN ? 1 : 0;
csr ^= SROMCLKOFF; EMIT; /* clock L data invalid */
}
sc->lmc_rombuf[idx*2] = data & 0xFF;
sc->lmc_rombuf[idx*2+1] = data >> 8;
csr = SROMSEL | SROMRD; EMIT;
csr = 0; EMIT;
}
lmc_srom_idle(sc);
}
#define MII_EMIT do { LMC_CSR_WRITE(sc, csr_srom_mii, csr); lmc_delay_300ns(sc); } while (0)
static inline void
lmc_mii_writebits(lmc_softc_t * const sc, unsigned data, unsigned bits)
{
unsigned msb = 1 << (bits - 1);
unsigned csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
unsigned lastbit = (csr & MII_DOUT) ? msb : 0;
csr |= MII_WR; MII_EMIT; /* clock low; assert write */
for (; bits > 0; bits--, data <<= 1) {
const unsigned thisbit = data & msb;
if (thisbit != lastbit) {
csr ^= MII_DOUT; MII_EMIT; /* clock low; invert data */
}
csr ^= MII_CLKON; MII_EMIT; /* clock high; data valid */
lastbit = thisbit;
csr ^= MII_CLKOFF; MII_EMIT; /* clock low; data not valid */
}
}
static void
lmc_mii_turnaround(lmc_softc_t * const sc, u_int32_t cmd)
{
u_int32_t csr;
csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
if (cmd == MII_WRCMD) {
csr |= MII_DOUT; MII_EMIT; /* clock low; change data */
csr ^= MII_CLKON; MII_EMIT; /* clock high; data valid */
csr ^= MII_CLKOFF; MII_EMIT; /* clock low; data not valid */
csr ^= MII_DOUT; MII_EMIT; /* clock low; change data */
} else {
csr |= MII_RD; MII_EMIT; /* clock low; switch to read */
}
csr ^= MII_CLKON; MII_EMIT; /* clock high; data valid */
csr ^= MII_CLKOFF; MII_EMIT; /* clock low; data not valid */
}
static u_int32_t
lmc_mii_readbits(lmc_softc_t * const sc)
{
u_int32_t data;
u_int32_t csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
int idx;
for (idx = 0, data = 0; idx < 16; idx++) {
data <<= 1; /* this is NOOP on the first pass through */
csr ^= MII_CLKON; MII_EMIT; /* clock high; data valid */
if (LMC_CSR_READ(sc, csr_srom_mii) & MII_DIN)
data |= 1;
csr ^= MII_CLKOFF; MII_EMIT; /* clock low; data not valid */
}
csr ^= MII_RD; MII_EMIT; /* clock low; turn off read */
return data;
}
u_int32_t
lmc_mii_readreg(lmc_softc_t * const sc, u_int32_t devaddr, u_int32_t regno)
{
u_int32_t csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
u_int32_t data;
csr &= ~(MII_RD|MII_CLK); MII_EMIT;
lmc_mii_writebits(sc, MII_PREAMBLE, 32);
lmc_mii_writebits(sc, MII_RDCMD, 8);
lmc_mii_writebits(sc, devaddr, 5);
lmc_mii_writebits(sc, regno, 5);
lmc_mii_turnaround(sc, MII_RDCMD);
data = lmc_mii_readbits(sc);
return (data);
}
void
lmc_mii_writereg(lmc_softc_t * const sc, u_int32_t devaddr,
u_int32_t regno, u_int32_t data)
{
u_int32_t csr;
csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
csr &= ~(MII_RD|MII_CLK); MII_EMIT;
lmc_mii_writebits(sc, MII_PREAMBLE, 32);
lmc_mii_writebits(sc, MII_WRCMD, 8);
lmc_mii_writebits(sc, devaddr, 5);
lmc_mii_writebits(sc, regno, 5);
lmc_mii_turnaround(sc, MII_WRCMD);
lmc_mii_writebits(sc, data, 16);
}
int
lmc_read_macaddr(lmc_softc_t * const sc)
{
lmc_srom_read(sc);
memcpy(sc->lmc_enaddr, sc->lmc_rombuf + 20, 6);
return 0;
}
/*
* Check to make certain there is a signal from the modem, and flicker
* lights as needed.
*/
#if defined(__NetBSD__) || defined(__FreeBSD__)
static void
lmc_watchdog(struct ifnet *ifp)
#endif
#if defined(__bsdi__)
static int
lmc_watchdog(int unit)
#endif
{
#if defined(__NetBSD__) || defined(__FreeBSD__)
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
#endif
#if defined(__bsdi__)
lmc_softc_t * const sc = LMC_UNIT_TO_SOFTC(unit);
struct ifnet *ifp = &sc->lmc_if;
#endif
int state;
u_int32_t ostatus;
u_int32_t link_status;
u_int32_t ticks;
state = 0;
/*
* Make sure the tx jabber and rx watchdog are off,
* and the transmit and receive processes are running.
*/
LMC_CSR_WRITE (sc, csr_15, 0x00000011);
sc->lmc_cmdmode |= TULIP_CMD_TXRUN | TULIP_CMD_RXRUN;
LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
/* Is the transmit clock still available? */
ticks = LMC_CSR_READ (sc, csr_gp_timer);
ticks = 0x0000ffff - (ticks & 0x0000ffff);
if (ticks == 0)
{
/* no clock found ? */
if (sc->tx_clockState != 0)
{
sc->tx_clockState = 0;
if (sc->lmc_cardtype == LMC_CARDTYPE_SSI)
lmc_led_on (sc, LMC_MII16_LED3); /* ON red */
}
else
if (sc->tx_clockState == 0)
{
sc->tx_clockState = 1;
if (sc->lmc_cardtype == LMC_CARDTYPE_SSI)
lmc_led_off (sc, LMC_MII16_LED3); /* OFF red */
}
}
link_status = sc->lmc_media->get_link_status(sc);
ostatus = ((sc->lmc_flags & LMC_MODEMOK) == LMC_MODEMOK);
/*
* hardware level link lost, but the interface is marked as up.
* Mark it as down.
*/
if (link_status == LMC_LINK_DOWN && ostatus) {
printf(LMC_PRINTF_FMT ": physical link down\n",
LMC_PRINTF_ARGS);
sc->lmc_flags &= ~LMC_MODEMOK;
if (sc->lmc_cardtype == LMC_CARDTYPE_DS3 ||
sc->lmc_cardtype == LMC_CARDTYPE_T1)
lmc_led_on (sc, LMC_DS3_LED3 | LMC_DS3_LED2);
/* turn on red LED */
else {
lmc_led_off (sc, LMC_MII16_LED1);
lmc_led_on (sc, LMC_MII16_LED0);
if (sc->lmc_timing == LMC_CTL_CLOCK_SOURCE_EXT)
lmc_led_on (sc, LMC_MII16_LED3);
}
}
/*
* hardware link is up, but the interface is marked as down.
* Bring it back up again.
*/
if (link_status != LMC_LINK_DOWN && !ostatus) {
printf(LMC_PRINTF_FMT ": physical link up\n",
LMC_PRINTF_ARGS);
if (sc->lmc_flags & LMC_IFUP) {
lmc_ifup(sc);
#if 0 && (defined(__NetBSD__) || defined(__FreeBSD__))
if (sc->lmc_if.if_flags & IFF_UP) {
struct sppp *sp = &sc->lmc_sppp;
/* re-connect LCP */
(sp->pp_down)(sp);
(sp->pp_up)(sp);
}
#endif
}
sc->lmc_flags |= LMC_MODEMOK;
if (sc->lmc_cardtype == LMC_CARDTYPE_DS3 ||
sc->lmc_cardtype == LMC_CARDTYPE_T1)
{
sc->lmc_miireg16 |= LMC_DS3_LED3;
lmc_led_off (sc, LMC_DS3_LED3);
/* turn off red LED */
lmc_led_on (sc, LMC_DS3_LED2);
} else {
lmc_led_on (sc, LMC_MII16_LED0 | LMC_MII16_LED1
| LMC_MII16_LED2);
if (sc->lmc_timing != LMC_CTL_CLOCK_SOURCE_EXT)
lmc_led_off (sc, LMC_MII16_LED3);
}
return;
}
/* Call media specific watchdog functions */
sc->lmc_media->watchdog(sc);
/*
* remember the timer value
*/
ticks = LMC_CSR_READ(sc, csr_gp_timer);
LMC_CSR_WRITE(sc, csr_gp_timer, 0xffffffffUL);
sc->ictl.ticks = 0x0000ffff - (ticks & 0x0000ffff);
ifp->if_timer = 1;
}
/*
* Mark the interface as "up" and enable TX/RX and TX/RX interrupts.
* This also does a full software reset.
*/
static void
lmc_ifup(lmc_softc_t * const sc)
{
sc->lmc_if.if_timer = 0;
lmc_dec_reset(sc);
lmc_reset(sc);
sc->lmc_media->set_link_status(sc, LMC_LINK_UP);
sc->lmc_media->set_status(sc, NULL);
sc->lmc_flags |= LMC_IFUP;
/*
* for DS3 & DS1 adapters light the green light, led2
*/
if (sc->lmc_cardtype == LMC_CARDTYPE_DS3 ||
sc->lmc_cardtype == LMC_CARDTYPE_T1)
lmc_led_on (sc, LMC_MII16_LED2);
else
lmc_led_on (sc, LMC_MII16_LED0 | LMC_MII16_LED2);
/*
* select what interrupts we want to get
*/
sc->lmc_intrmask |= (TULIP_STS_NORMALINTR
| TULIP_STS_RXINTR
| TULIP_STS_RXNOBUF
| TULIP_STS_TXINTR
| TULIP_STS_ABNRMLINTR
| TULIP_STS_SYSERROR
| TULIP_STS_TXSTOPPED
| TULIP_STS_TXUNDERFLOW
| TULIP_STS_RXSTOPPED
);
LMC_CSR_WRITE(sc, csr_intr, sc->lmc_intrmask);
sc->lmc_cmdmode |= TULIP_CMD_TXRUN;
sc->lmc_cmdmode |= TULIP_CMD_RXRUN;
LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode);
sc->lmc_if.if_timer = 1;
}
/*
* Mark the interface as "down" and disable TX/RX and TX/RX interrupts.
* This is done by performing a full reset on the interface.
*/
static void
lmc_ifdown(lmc_softc_t * const sc)
{
#if 0 && (defined(__NetBSD__) || defined(__FreeBSD__))
if (sc->lmc_if.if_flags & IFF_UP) {
struct sppp *sp = &sc->lmc_sppp;
/* disconnect LCP */
(sp->pp_down)(sp);
}
#endif
sc->lmc_if.if_timer = 0;
sc->lmc_flags &= ~LMC_IFUP;
sc->lmc_media->set_link_status(sc, LMC_LINK_DOWN);
lmc_led_off(sc, LMC_MII16_LED_ALL);
lmc_dec_reset(sc);
lmc_reset(sc);
sc->lmc_media->set_status(sc, NULL);
}
static void
lmc_rx_intr(lmc_softc_t * const sc)
{
lmc_ringinfo_t * const ri = &sc->lmc_rxinfo;
struct ifnet * const ifp = &sc->lmc_if;
u_int32_t status;
int fillok = 1;
sc->lmc_rxtick++;
for (;;) {
lmc_desc_t *eop = ri->ri_nextin;
int total_len = 0, last_offset = 0;
struct mbuf *ms = NULL, *me = NULL;
int accept = 0;
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NORX)
bus_dmamap_t map;
int error;
#endif
if (fillok && sc->lmc_rxq.ifq_len < LMC_RXQ_TARGET)
goto queue_mbuf;
/*
* If the TULIP has no descriptors, there can't be any receive
* descriptors to process.
*/
if (eop == ri->ri_nextout)
break;
/*
* 90% of the packets will fit in one descriptor. So we
* optimize for that case.
*/
LMC_RXDESC_POSTSYNC(sc, eop, sizeof(*eop));
status = le32toh(((volatile lmc_desc_t *) eop)->d_status);
if ((status &
(TULIP_DSTS_OWNER|TULIP_DSTS_RxFIRSTDESC|TULIP_DSTS_RxLASTDESC)) ==
(TULIP_DSTS_RxFIRSTDESC|TULIP_DSTS_RxLASTDESC)) {
IF_DEQUEUE(&sc->lmc_rxq, ms);
me = ms;
} else {
/*
* If still owned by the TULIP, don't touch it.
*/
if (status & TULIP_DSTS_OWNER)
break;
/*
* It is possible (though improbable unless the
* BIG_PACKET support is enabled or MCLBYTES < 1518)
* for a received packet to cross more than one
* receive descriptor.
*/
while ((status & TULIP_DSTS_RxLASTDESC) == 0) {
if (++eop == ri->ri_last)
eop = ri->ri_first;
LMC_RXDESC_POSTSYNC(sc, eop, sizeof(*eop));
status = le32toh(((volatile lmc_desc_t *)
eop)->d_status);
if (eop == ri->ri_nextout ||
(status & TULIP_DSTS_OWNER)) {
return;
}
total_len++;
}
/*
* Dequeue the first buffer for the start of the
* packet. Hopefully this will be the only one we
* need to dequeue. However, if the packet consumed
* multiple descriptors, then we need to dequeue
* those buffers and chain to the starting mbuf.
* All buffers but the last buffer have the same
* length so we can set that now. (we add to
* last_offset instead of multiplying since we
* normally won't go into the loop and thereby
* saving a ourselves from doing a multiplication
* by 0 in the normal case).
*/
IF_DEQUEUE(&sc->lmc_rxq, ms);
for (me = ms; total_len > 0; total_len--) {
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NORX)
map = M_GETCTX(me, bus_dmamap_t);
LMC_RXMAP_POSTSYNC(sc, map);
bus_dmamap_unload(sc->lmc_dmatag, map);
sc->lmc_rxmaps[sc->lmc_rxmaps_free++] = map;
#if defined(DIAGNOSTIC)
M_SETCTX(me, NULL);
#endif
#endif /* LMC_BUS_DMA */
me->m_len = LMC_RX_BUFLEN;
last_offset += LMC_RX_BUFLEN;
IF_DEQUEUE(&sc->lmc_rxq, me->m_next);
me = me->m_next;
}
}
/*
* Now get the size of received packet (minus the CRC).
*/
total_len = ((status >> 16) & 0x7FFF);
if (sc->ictl.crc_length == 16)
total_len -= 2;
else
total_len -= 4;
if ((sc->lmc_flags & LMC_RXIGNORE) == 0
&& ((status & LMC_DSTS_ERRSUM) == 0
#ifdef BIG_PACKET
|| (total_len <= sc->lmc_if.if_mtu + PPP_HEADER_LEN
&& (status & TULIP_DSTS_RxOVERFLOW) == 0)
#endif
)) {
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NORX)
map = M_GETCTX(me, bus_dmamap_t);
bus_dmamap_sync(sc->lmc_dmatag, map, 0, me->m_len,
BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->lmc_dmatag, map);
sc->lmc_rxmaps[sc->lmc_rxmaps_free++] = map;
#if defined(DIAGNOSTIC)
M_SETCTX(me, NULL);
#endif
#endif /* LMC_BUS_DMA */
me->m_len = total_len - last_offset;
#if NBPFILTER > 0
if (sc->lmc_bpf != NULL) {
if (me == ms)
LMC_BPF_TAP(sc, mtod(ms, caddr_t), total_len);
else
LMC_BPF_MTAP(sc, ms);
}
#endif
sc->lmc_flags |= LMC_RXACT;
accept = 1;
} else {
ifp->if_ierrors++;
if (status & TULIP_DSTS_RxOVERFLOW) {
sc->lmc_dot3stats.dot3StatsInternalMacReceiveErrors++;
}
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NORX)
map = M_GETCTX(me, bus_dmamap_t);
bus_dmamap_unload(sc->lmc_dmatag, map);
sc->lmc_rxmaps[sc->lmc_rxmaps_free++] = map;
#if defined(DIAGNOSTIC)
M_SETCTX(me, NULL);
#endif
#endif /* LMC_BUS_DMA */
}
ifp->if_ipackets++;
if (++eop == ri->ri_last)
eop = ri->ri_first;
ri->ri_nextin = eop;
queue_mbuf:
/*
* Either we are priming the TULIP with mbufs (m == NULL)
* or we are about to accept an mbuf for the upper layers
* so we need to allocate an mbuf to replace it. If we
* can't replace it, send up it anyways. This may cause
* us to drop packets in the future but that's better than
* being caught in livelock.
*
* Note that if this packet crossed multiple descriptors
* we don't even try to reallocate all the mbufs here.
* Instead we rely on the test of the beginning of
* the loop to refill for the extra consumed mbufs.
*/
if (accept || ms == NULL) {
struct mbuf *m0;
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 != NULL) {
MCLGET(m0, M_DONTWAIT);
if ((m0->m_flags & M_EXT) == 0) {
m_freem(m0);
m0 = NULL;
}
}
if (accept) {
ms->m_pkthdr.len = total_len;
ms->m_pkthdr.rcvif = ifp;
#if defined(__NetBSD__) || defined(__FreeBSD__)
sppp_input(ifp, ms);
#endif
#if defined(__bsdi__)
sc->lmc_p2pcom.p2p_input(&sc->lmc_p2pcom, ms);
#endif
}
ms = m0;
}
if (ms == NULL) {
/*
* Couldn't allocate a new buffer. Don't bother
* trying to replenish the receive queue.
*/
fillok = 0;
sc->lmc_flags |= LMC_RXBUFSLOW;
continue;
}
/*
* Now give the buffer(s) to the TULIP and save in our
* receive queue.
*/
do {
u_int32_t ctl;
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NORX)
lmc_desc_t * const nextout = ri->ri_nextout;
if (sc->lmc_rxmaps_free > 0) {
map = sc->lmc_rxmaps[--sc->lmc_rxmaps_free];
} else {
m_freem(ms);
sc->lmc_flags |= LMC_RXBUFSLOW;
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_rxlowbufs++;
#endif
break;
}
M_SETCTX(ms, map);
error = bus_dmamap_load(sc->lmc_dmatag, map,
mtod(ms, void *), LMC_RX_BUFLEN,
NULL, BUS_DMA_NOWAIT);
if (error) {
printf(LMC_PRINTF_FMT
": unable to load rx map, "
"error = %d\n",
LMC_PRINTF_ARGS, error);
panic("lmc_rx_intr"); /* XXX */
}
ctl = le32toh(nextout->d_ctl);
/* For some weird reason we lose TULIP_DFLAG_ENDRING */
if ((nextout+1) == ri->ri_last)
ctl = LMC_CTL(LMC_CTL_FLGS(ctl)|
TULIP_DFLAG_ENDRING, 0, 0);
nextout->d_addr1 = htole32(map->dm_segs[0].ds_addr);
if (map->dm_nsegs == 2) {
nextout->d_addr2 = htole32(map->dm_segs[1].ds_addr);
nextout->d_ctl =
htole32(LMC_CTL(LMC_CTL_FLGS(ctl),
map->dm_segs[0].ds_len,
map->dm_segs[1].ds_len));
} else {
nextout->d_addr2 = 0;
nextout->d_ctl =
htole32(LMC_CTL(LMC_CTL_FLGS(ctl),
map->dm_segs[0].ds_len, 0));
}
LMC_RXDESC_POSTSYNC(sc, nextout, sizeof(*nextout));
#else /* LMC_BUS_DMA */
ctl = le32toh(ri->ri_nextout->d_ctl);
ri->ri_nextout->d_addr1 = htole32(LMC_KVATOPHYS(sc,
mtod(ms, caddr_t)));
ri->ri_nextout->d_ctl = htole32(LMC_CTL(LMC_CTL_FLGS(ctl),
LMC_RX_BUFLEN, 0));
#endif /* LMC_BUS_DMA */
ri->ri_nextout->d_status = htole32(TULIP_DSTS_OWNER);
LMC_RXDESC_POSTSYNC(sc, nextout, sizeof(u_int32_t));
if (++ri->ri_nextout == ri->ri_last)
ri->ri_nextout = ri->ri_first;
me = ms->m_next;
ms->m_next = NULL;
IF_ENQUEUE(&sc->lmc_rxq, ms);
} while ((ms = me) != NULL);
if (sc->lmc_rxq.ifq_len >= LMC_RXQ_TARGET)
sc->lmc_flags &= ~LMC_RXBUFSLOW;
}
}
static int
lmc_tx_intr(lmc_softc_t * const sc)
{
lmc_ringinfo_t * const ri = &sc->lmc_txinfo;
struct mbuf *m;
int xmits = 0;
int descs = 0;
u_int32_t d_status;
sc->lmc_txtick++;
while (ri->ri_free < ri->ri_max) {
u_int32_t flag;
LMC_TXDESC_POSTSYNC(sc, ri->ri_nextin, sizeof(*ri->ri_nextin));
d_status = le32toh(((volatile lmc_desc_t *) ri->ri_nextin)->d_status);
if (d_status & TULIP_DSTS_OWNER)
break;
flag = LMC_CTL_FLGS(le32toh(ri->ri_nextin->d_ctl));
if (flag & TULIP_DFLAG_TxLASTSEG) {
IF_DEQUEUE(&sc->lmc_txq, m);
if (m != NULL) {
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NOTX)
bus_dmamap_t map = M_GETCTX(m, bus_dmamap_t);
LMC_TXMAP_POSTSYNC(sc, map);
sc->lmc_txmaps[sc->lmc_txmaps_free++] = map;
#endif /* LMC_BUS_DMA */
#if NBPFILTER > 0
if (sc->lmc_bpf != NULL)
LMC_BPF_MTAP(sc, m);
#endif
m_freem(m);
#if defined(LMC_DEBUG)
} else {
printf(LMC_PRINTF_FMT ": tx_intr: failed to dequeue mbuf?!?\n", LMC_PRINTF_ARGS);
#endif
}
xmits++;
if (d_status & LMC_DSTS_ERRSUM) {
sc->lmc_if.if_oerrors++;
if (d_status & TULIP_DSTS_TxUNDERFLOW) {
sc->lmc_dot3stats.dot3StatsInternalTransmitUnderflows++;
}
} else {
if (d_status & TULIP_DSTS_TxDEFERRED) {
sc->lmc_dot3stats.dot3StatsDeferredTransmissions++;
}
}
}
if (++ri->ri_nextin == ri->ri_last)
ri->ri_nextin = ri->ri_first;
ri->ri_free++;
descs++;
sc->lmc_if.if_flags &= ~IFF_OACTIVE;
}
/*
* If nothing left to transmit, disable the timer.
* Else if progress, reset the timer back to 2 ticks.
*/
sc->lmc_if.if_opackets += xmits;
return descs;
}
static void
lmc_print_abnormal_interrupt (lmc_softc_t * const sc, u_int32_t csr)
{
printf(LMC_PRINTF_FMT ": Abnormal interrupt\n", LMC_PRINTF_ARGS);
}
static void
lmc_intr_handler(lmc_softc_t * const sc, int *progress_p)
{
u_int32_t csr;
while ((csr = LMC_CSR_READ(sc, csr_status)) & sc->lmc_intrmask) {
#if defined(__NetBSD__)
#if NRND > 0
rnd_add_uint32(&sc->lmc_rndsource, csr);
#endif
#endif
*progress_p = 1;
LMC_CSR_WRITE(sc, csr_status, csr);
if (csr & TULIP_STS_SYSERROR) {
sc->lmc_last_system_error = (csr & TULIP_STS_ERRORMASK) >> TULIP_STS_ERR_SHIFT;
if (sc->lmc_flags & LMC_NOMESSAGES) {
sc->lmc_flags |= LMC_SYSTEMERROR;
} else {
printf(LMC_PRINTF_FMT ": system error: %s\n",
LMC_PRINTF_ARGS,
lmc_system_errors[sc->lmc_last_system_error]);
}
sc->lmc_flags |= LMC_NEEDRESET;
sc->lmc_system_errors++;
break;
}
if (csr & (TULIP_STS_RXINTR | TULIP_STS_RXNOBUF)) {
u_int32_t misses = LMC_CSR_READ(sc, csr_missed_frames);
if (csr & TULIP_STS_RXNOBUF)
sc->lmc_dot3stats.dot3StatsMissedFrames += misses & 0xFFFF;
/*
* Pass 2.[012] of the 21140A-A[CDE] may hang and/or corrupt data
* on receive overflows.
*/
if ((misses & 0x0FFE0000) && (sc->lmc_features & LMC_HAVE_RXBADOVRFLW)) {
sc->lmc_dot3stats.dot3StatsInternalMacReceiveErrors++;
/*
* Stop the receiver process and spin until it's stopped.
* Tell rx_intr to drop the packets it dequeues.
*/
LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode & ~TULIP_CMD_RXRUN);
while ((LMC_CSR_READ(sc, csr_status) & TULIP_STS_RXSTOPPED) == 0)
;
LMC_CSR_WRITE(sc, csr_status, TULIP_STS_RXSTOPPED);
sc->lmc_flags |= LMC_RXIGNORE;
}
lmc_rx_intr(sc);
if (sc->lmc_flags & LMC_RXIGNORE) {
/*
* Restart the receiver.
*/
sc->lmc_flags &= ~LMC_RXIGNORE;
LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode);
}
}
if (csr & TULIP_STS_ABNRMLINTR) {
u_int32_t tmp = csr & sc->lmc_intrmask
& ~(TULIP_STS_NORMALINTR|TULIP_STS_ABNRMLINTR);
if (csr & TULIP_STS_TXUNDERFLOW) {
if ((sc->lmc_cmdmode & TULIP_CMD_THRESHOLDCTL) != TULIP_CMD_THRSHLD160) {
sc->lmc_cmdmode += TULIP_CMD_THRSHLD96;
sc->lmc_flags |= LMC_NEWTXTHRESH;
} else if (sc->lmc_features & LMC_HAVE_STOREFWD) {
sc->lmc_cmdmode |= TULIP_CMD_STOREFWD;
sc->lmc_flags |= LMC_NEWTXTHRESH;
}
}
if (sc->lmc_flags & LMC_NOMESSAGES) {
sc->lmc_statusbits |= tmp;
} else {
lmc_print_abnormal_interrupt(sc, tmp);
sc->lmc_flags |= LMC_NOMESSAGES;
}
LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode);
}
if (csr & TULIP_STS_TXINTR)
lmc_tx_intr(sc);
if (sc->lmc_flags & LMC_WANTTXSTART)
lmc_ifstart(&sc->lmc_if);
}
}
lmc_intrfunc_t
lmc_intr_normal(void *arg)
{
lmc_softc_t * sc = (lmc_softc_t *) arg;
int progress = 0;
lmc_intr_handler(sc, &progress);
#if !defined(LMC_VOID_INTRFUNC)
return progress;
#endif
}
static struct mbuf *
lmc_mbuf_compress(struct mbuf *m)
{
struct mbuf *m0;
#if MCLBYTES >= LMC_MTU + PPP_HEADER_LEN && !defined(BIG_PACKET)
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 != NULL) {
if (m->m_pkthdr.len > MHLEN) {
MCLGET(m0, M_DONTWAIT);
if ((m0->m_flags & M_EXT) == 0) {
m_freem(m);
m_freem(m0);
return NULL;
}
}
m_copydata(m, 0, m->m_pkthdr.len, mtod(m0, caddr_t));
m0->m_pkthdr.len = m0->m_len = m->m_pkthdr.len;
}
#else
int mlen = MHLEN;
int len = m->m_pkthdr.len;
struct mbuf **mp = &m0;
while (len > 0) {
if (mlen == MHLEN) {
MGETHDR(*mp, M_DONTWAIT, MT_DATA);
} else {
MGET(*mp, M_DONTWAIT, MT_DATA);
}
if (*mp == NULL) {
m_freem(m0);
m0 = NULL;
break;
}
if (len > MLEN) {
MCLGET(*mp, M_DONTWAIT);
if (((*mp)->m_flags & M_EXT) == 0) {
m_freem(m0);
m0 = NULL;
break;
}
(*mp)->m_len = (len <= MCLBYTES ? len : MCLBYTES);
} else {
(*mp)->m_len = (len <= mlen ? len : mlen);
}
m_copydata(m, m->m_pkthdr.len - len,
(*mp)->m_len, mtod((*mp), caddr_t));
len -= (*mp)->m_len;
mp = &(*mp)->m_next;
mlen = MLEN;
}
#endif
m_freem(m);
return m0;
}
/*
* queue the mbuf handed to us for the interface. If we cannot
* queue it, return the mbuf. Return NULL if the mbuf was queued.
*/
static struct mbuf *
lmc_txput(lmc_softc_t * const sc, struct mbuf *m)
{
lmc_ringinfo_t * const ri = &sc->lmc_txinfo;
lmc_desc_t *eop, *nextout;
int segcnt, free;
u_int32_t d_status, ctl;
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NOTX)
bus_dmamap_t map;
int error;
#else
struct mbuf *m0;
#endif
#if defined(LMC_DEBUG)
if ((sc->lmc_cmdmode & TULIP_CMD_TXRUN) == 0) {
printf(LMC_PRINTF_FMT ": txput: tx not running\n",
LMC_PRINTF_ARGS);
sc->lmc_flags |= LMC_WANTTXSTART;
goto finish;
}
#endif
/*
* Now we try to fill in our transmit descriptors. This is
* a bit reminiscent of going on the Ark two by two
* since each descriptor for the TULIP can describe
* two buffers. So we advance through packet filling
* each of the two entries at a time to fill each
* descriptor. Clear the first and last segment bits
* in each descriptor (actually just clear everything
* but the end-of-ring or chain bits) to make sure
* we don't get messed up by previously sent packets.
*
* We may fail to put the entire packet on the ring if
* there is either not enough ring entries free or if the
* packet has more than MAX_TXSEG segments. In the former
* case we will just wait for the ring to empty. In the
* latter case we have to recopy.
*/
#if !defined(LMC_BUS_DMA) || defined(LMC_BUS_DMA_NOTX)
again:
m0 = m;
#endif
d_status = 0;
eop = nextout = ri->ri_nextout;
segcnt = 0;
free = ri->ri_free;
#if defined(LMC_BUS_DMA) && !defined(LMC_BUS_DMA_NOTX)
/*
* Reclaim some dma maps from if we are out.
*/
if (sc->lmc_txmaps_free == 0) {
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_no_txmaps++;
#endif
free += lmc_tx_intr(sc);
}
if (sc->lmc_txmaps_free > 0) {
map = sc->lmc_txmaps[sc->lmc_txmaps_free-1];
} else {
sc->lmc_flags |= LMC_WANTTXSTART;
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_txput_finishes[1]++;
#endif
goto finish;
}
error = bus_dmamap_load_mbuf(sc->lmc_dmatag, map, m, BUS_DMA_NOWAIT);
if (error != 0) {
if (error == EFBIG) {
/*
* The packet exceeds the number of transmit buffer
* entries that we can use for one packet, so we have
* to recopy it into one mbuf and then try again.
*/
m = lmc_mbuf_compress(m);
if (m == NULL) {
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_txput_finishes[2]++;
#endif
goto finish;
}
error = bus_dmamap_load_mbuf(sc->lmc_dmatag, map, m,
BUS_DMA_NOWAIT);
}
if (error != 0) {
printf(LMC_PRINTF_FMT ": unable to load tx map, "
"error = %d\n", LMC_PRINTF_ARGS, error);
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_txput_finishes[3]++;
#endif
goto finish;
}
}
if ((free -= (map->dm_nsegs + 1) / 2) <= 0
/*
* See if there's any unclaimed space in the transmit ring.
*/
&& (free += lmc_tx_intr(sc)) <= 0) {
/*
* There's no more room but since nothing
* has been committed at this point, just
* show output is active, put back the
* mbuf and return.
*/
sc->lmc_flags |= LMC_WANTTXSTART;
#if defined(LMC_DEBUG)
sc->lmc_dbg.dbg_txput_finishes[4]++;
#endif
bus_dmamap_unload(sc->lmc_dmatag, map);
goto finish;
}
for (; map->dm_nsegs - segcnt > 1; segcnt += 2) {
int flg;
eop = nextout;
flg = LMC_CTL_FLGS(le32toh(eop->d_ctl));
flg &= TULIP_DFLAG_ENDRING;
flg |= TULIP_DFLAG_TxNOPADDING;
if (sc->ictl.crc_length == 16)
flg |= TULIP_DFLAG_TxHASCRC;
eop->d_status = htole32(d_status);
eop->d_addr1 = htole32(map->dm_segs[segcnt].ds_addr);
eop->d_addr2 = htole32(map->dm_segs[segcnt+1].ds_addr);
eop->d_ctl = htole32(LMC_CTL(flg,
map->dm_segs[segcnt].ds_len,
map->dm_segs[segcnt+1].ds_len));
d_status = TULIP_DSTS_OWNER;
if (++nextout == ri->ri_last)
nextout = ri->ri_first;
}
if (segcnt < map->dm_nsegs) {
int flg;
eop = nextout;
flg = LMC_CTL_FLGS(le32toh(eop->d_ctl));
flg &= TULIP_DFLAG_ENDRING;
flg |= TULIP_DFLAG_TxNOPADDING;
if (sc->ictl.crc_length == 16)
flg |= TULIP_DFLAG_TxHASCRC;
eop->d_status = htole32(d_status);
eop->d_addr1 = htole32(map->dm_segs[segcnt].ds_addr);
eop->d_addr2 = 0;
eop->d_ctl = htole32(LMC_CTL(flg,
map->dm_segs[segcnt].ds_len, 0));
if (++nextout == ri->ri_last)
nextout = ri->ri_first;
}
LMC_TXMAP_PRESYNC(sc, map);
M_SETCTX(m, map);
map = NULL;
--sc->lmc_txmaps_free; /* commit to using the dmamap */
#else /* !LMC_BUS_DMA */
do {
int len = m0->m_len;
caddr_t addr = mtod(m0, caddr_t);
unsigned clsize = PAGE_SIZE - (((u_long) addr) & PAGE_MASK);
while (len > 0) {
unsigned slen = min(len, clsize);
#ifdef BIG_PACKET
int partial = 0;
if (slen >= 2048)
slen = 2040, partial = 1;
#endif
segcnt++;
if (segcnt > LMC_MAX_TXSEG) {
/*
* The packet exceeds the number of transmit
* buffer entries that we can use for one
* packet, so we have recopy it into one mbuf
* and then try again.
*/
m = lmc_mbuf_compress(m);
if (m == NULL)
goto finish;
goto again;
}
if (segcnt & 1) {
if (--free == 0) {
/*
* See if there's any unclaimed space
* in the transmit ring.
*/
if ((free += lmc_tx_intr(sc)) == 0) {
/*
* There's no more room but
* since nothing has been
* committed at this point,
* just show output is active,
* put back the mbuf and
* return.
*/
sc->lmc_flags |= LMC_WANTTXSTART;
goto finish;
}
}
eop = nextout;
if (++nextout == ri->ri_last)
nextout = ri->ri_first;
eop->d_flag &= TULIP_DFLAG_ENDRING;
eop->d_flag |= TULIP_DFLAG_TxNOPADDING;
if (sc->ictl.crc_length == 16)
eop->d_flag |= TULIP_DFLAG_TxHASCRC;
eop->d_status = d_status;
eop->d_addr1 = LMC_KVATOPHYS(sc, addr);
eop->d_length1 = slen;
} else {
/*
* Fill in second half of descriptor
*/
eop->d_addr2 = LMC_KVATOPHYS(sc, addr);
eop->d_length2 = slen;
}
d_status = TULIP_DSTS_OWNER;
len -= slen;
addr += slen;
#ifdef BIG_PACKET
if (partial)
continue;
#endif
clsize = PAGE_SIZE;
}
} while ((m0 = m0->m_next) != NULL);
#endif /* LMC_BUS_DMA */
/*
* The descriptors have been filled in. Now get ready
* to transmit.
*/
IF_ENQUEUE(&sc->lmc_txq, m);
m = NULL;
/*
* Make sure the next descriptor after this packet is owned
* by us since it may have been set up above if we ran out
* of room in the ring.
*/
nextout->d_status = 0;
LMC_TXDESC_PRESYNC(sc, nextout, sizeof(u_int32_t));
#if !defined(LMC_BUS_DMA) || defined(LMC_BUS_DMA_NOTX)
/*
* If we only used the first segment of the last descriptor,
* make sure the second segment will not be used.
*/
if (segcnt & 1) {
eop->d_addr2 = 0;
eop->d_length2 = 0;
}
#endif /* LMC_BUS_DMA */
/*
* Mark the last and first segments, indicate we want a transmit
* complete interrupt, and tell it to transmit!
*/
ctl = le32toh(eop->d_ctl);
eop->d_ctl = htole32(LMC_CTL(
LMC_CTL_FLGS(ctl)|TULIP_DFLAG_TxLASTSEG|TULIP_DFLAG_TxWANTINTR,
LMC_CTL_LEN1(ctl),
LMC_CTL_LEN2(ctl)));
/*
* Note that ri->ri_nextout is still the start of the packet
* and until we set the OWNER bit, we can still back out of
* everything we have done.
*/
ctl = le32toh(ri->ri_nextout->d_ctl);
ri->ri_nextout->d_ctl = htole32(LMC_CTL(
LMC_CTL_FLGS(ctl)|TULIP_DFLAG_TxFIRSTSEG,
LMC_CTL_LEN1(ctl),
LMC_CTL_LEN2(ctl)));
#if defined(LMC_BUS_MAP) && !defined(LMC_BUS_DMA_NOTX)
if (eop < ri->ri_nextout) {
LMC_TXDESC_PRESYNC(sc, ri->ri_nextout,
(caddr_t) ri->ri_last - (caddr_t) ri->ri_nextout);
LMC_TXDESC_PRESYNC(sc, ri->ri_first,
(caddr_t) (eop + 1) - (caddr_t) ri->ri_first);
} else {
LMC_TXDESC_PRESYNC(sc, ri->ri_nextout,
(caddr_t) (eop + 1) - (caddr_t) ri->ri_nextout);
}
#endif
ri->ri_nextout->d_status = htole32(TULIP_DSTS_OWNER);
LMC_TXDESC_PRESYNC(sc, ri->ri_nextout, sizeof(u_int32_t));
LMC_CSR_WRITE(sc, csr_txpoll, 1);
/*
* This advances the ring for us.
*/
ri->ri_nextout = nextout;
ri->ri_free = free;
/*
* switch back to the single queueing ifstart.
*/
sc->lmc_flags &= ~LMC_WANTTXSTART;
sc->lmc_if.if_start = lmc_ifstart_one;
/*
* If we want a txstart, there must be not enough space in the
* transmit ring. So we want to enable transmit done interrupts
* so we can immediately reclaim some space. When the transmit
* interrupt is posted, the interrupt handler will call tx_intr
* to reclaim space and then txstart (since WANTTXSTART is set).
* txstart will move the packet into the transmit ring and clear
* WANTTXSTART thereby causing TXINTR to be cleared.
*/
finish:
if (sc->lmc_flags & LMC_WANTTXSTART) {
sc->lmc_if.if_flags |= IFF_OACTIVE;
sc->lmc_if.if_start = lmc_ifstart;
}
return m;
}
/*
* This routine is entered at splnet() (splsoftnet() on NetBSD)
*/
static int
lmc_ifioctl(struct ifnet * ifp, ioctl_cmd_t cmd, caddr_t data)
{
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
#if defined(__NetBSD__) || defined(__FreeBSD__)
lmc_spl_t s;
#endif
int error = 0;
struct ifreq *ifr = (struct ifreq *)data;
u_int32_t new_state;
u_int32_t old_state;
lmc_ctl_t ctl;
#if defined(__NetBSD__) || defined(__FreeBSD__)
s = LMC_RAISESPL();
#endif
switch (cmd) {
case LMCIOCGINFO:
error = copyout(&sc->ictl, ifr->ifr_data, sizeof(lmc_ctl_t));
goto out;
break;
case LMCIOCSINFO:
#if 0 /* XXX */
error = suser(p->p_ucred, &p->p_acflag);
if (error)
goto out;
#endif
error = copyin(ifr->ifr_data, &ctl, sizeof(lmc_ctl_t));
if (error != 0)
goto out;
sc->lmc_media->set_status(sc, &ctl);
goto out;
break;
#if defined(__NetBSD__) || defined(__FreeBSD__)
case SIOCSIFMTU:
/*
* Don't allow the MTU to get larger than we can handle
*/
if (ifr->ifr_mtu > LMC_MTU) {
error = EINVAL;
goto out;
} else {
ifp->if_mtu = ifr->ifr_mtu;
}
break;
#endif
}
#if defined(__NetBSD__) || defined(__FreeBSD__)
/*
* call the sppp ioctl layer
*/
error = sppp_ioctl(ifp, cmd, data);
if (error != 0)
goto out;
#endif
#if defined(__bsdi__)
error = p2p_ioctl(ifp, cmd, data);
#endif
#if defined(__NetBSD__) || defined(__FreeBSD__)
/*
* If we are transitioning from up to down or down to up, call
* our init routine.
*/
new_state = ifp->if_flags & IFF_UP;
old_state = sc->lmc_flags & LMC_IFUP;
if (new_state && !old_state)
lmc_ifup(sc);
else if (!new_state && old_state)
lmc_ifdown(sc);
#endif
out:
#if defined(__NetBSD__) || defined(__FreeBSD__)
LMC_RESTORESPL(s);
#endif
return error;
}
/*
* These routines gets called at device spl (from sppp_output).
*/
#if defined(__NetBSD__) || defined(__FreeBSD__)
static ifnet_ret_t
lmc_ifstart(struct ifnet * const ifp)
{
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
struct mbuf *m;
if (sc->lmc_flags & LMC_IFUP) {
while (sppp_isempty(ifp) == 0) {
m = sppp_dequeue(ifp);
if (!m)
break;
if ((m = lmc_txput(sc, m)) != NULL) {
IF_PREPEND(&((struct sppp *)ifp)->pp_fastq, m);
break;
}
}
LMC_CSR_WRITE(sc, csr_txpoll, 1);
}
}
static ifnet_ret_t
lmc_ifstart_one(struct ifnet * const ifp)
{
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
struct mbuf *m;
if ((sc->lmc_flags & LMC_IFUP) && (sppp_isempty(ifp) == 0)) {
m = sppp_dequeue(ifp);
if (m) {
if ((m = lmc_txput(sc, m)) != NULL)
IF_PREPEND(&((struct sppp *)ifp)->pp_fastq, m);
}
LMC_CSR_WRITE(sc, csr_txpoll, 1);
}
}
#endif
#if defined(__bsdi__)
static ifnet_ret_t
lmc_ifstart(struct ifnet * const ifp)
{
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
struct mbuf *m;
struct ifqueue *ifq;
if ((sc->lmc_flags & LMC_IFUP) == 0)
return;
for (;;) {
ifq = &sc->lmc_p2pcom.p2p_isnd;
m = ifq->ifq_head;
if (m == NULL) {
ifq = &sc->lmc_if.if_snd;
m = ifq->ifq_head;
}
if (m == NULL)
break;
IF_DEQUEUE(ifq, m);
m = lmc_txput(sc, m);
if (m != NULL) {
IF_PREPEND(ifq, m);
break;
}
}
LMC_CSR_WRITE(sc, csr_txpoll, 1);
}
static ifnet_ret_t
lmc_ifstart_one(struct ifnet * const ifp)
{
lmc_softc_t * const sc = LMC_IFP_TO_SOFTC(ifp);
struct mbuf *m;
struct ifqueue *ifq;
if ((sc->lmc_flags & LMC_IFUP) == 0)
return;
ifq = &sc->lmc_p2pcom.p2p_isnd;
m = ifq->ifq_head;
if (m == NULL) {
ifq = &sc->lmc_if.if_snd;
m = ifq->ifq_head;
}
if (m == NULL)
return 0;
IF_DEQUEUE(ifq, m);
m = lmc_txput(sc, m);
if (m != NULL)
IF_PREPEND(ifq, m);
LMC_CSR_WRITE(sc, csr_txpoll, 1);
}
#endif
#if defined(__bsdi__)
int
lmc_getmdm(struct p2pcom *pp, caddr_t b)
{
lmc_softc_t *sc = LMC_UNIT_TO_SOFTC(pp->p2p_if.if_unit);
if (sc->lmc_media->get_link_status(sc)) {
*(int *)b = TIOCM_CAR;
} else {
*(int *)b = 0;
}
return (0);
}
int
lmc_mdmctl(struct p2pcom *pp, int flag)
{
lmc_softc_t *sc = LMC_UNIT_TO_SOFTC(pp->p2p_if.if_unit);
sc->lmc_media->set_link_status(sc, flag);
if (flag)
if ((sc->lmc_flags & LMC_IFUP) == 0)
lmc_ifup(sc);
else
if ((sc->lmc_flags & LMC_IFUP) == LMC_IFUP)
lmc_ifdown(sc);
return (0);
}
#endif
/*
* Set up the OS interface magic and attach to the operating system
* network services.
*/
void
lmc_attach(lmc_softc_t * const sc)
{
struct ifnet * const ifp = &sc->lmc_if;
ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST;
ifp->if_ioctl = lmc_ifioctl;
ifp->if_start = lmc_ifstart;
ifp->if_watchdog = lmc_watchdog;
ifp->if_timer = 1;
ifp->if_mtu = LMC_MTU;
#if defined(__bsdi__)
ifp->if_type = IFT_NONE;
ifp->if_unit = (sc->lmc_dev.dv_unit);
#endif
if_attach(ifp);
#if defined(__NetBSD__) || defined(__FreeBSD__)
sc->lmc_sppp.pp_framebytes = 3; /* 1 flag byte, 2 byte FCS */
sppp_attach((struct ifnet *)&sc->lmc_sppp);
sc->lmc_sppp.pp_flags = PP_CISCO | PP_KEEPALIVE;
#endif
#if defined(__bsdi__)
sc->lmc_p2pcom.p2p_mdmctl = lmc_mdmctl;
sc->lmc_p2pcom.p2p_getmdm = lmc_getmdm;
p2p_attach(&sc->lmc_p2pcom);
#endif
#if NBPFILTER > 0
LMC_BPF_ATTACH(sc);
#endif
#if defined(__NetBSD__) && NRND > 0
rnd_attach_source(&sc->lmc_rndsource, sc->lmc_dev.dv_xname,
RND_TYPE_NET, 0);
#endif
/*
* turn off those LEDs...
*/
sc->lmc_miireg16 |= LMC_MII16_LED_ALL;
lmc_led_on(sc, LMC_MII16_LED0);
}
void
lmc_initring(lmc_softc_t * const sc, lmc_ringinfo_t * const ri,
lmc_desc_t *descs, int ndescs)
{
ri->ri_max = ndescs;
ri->ri_first = descs;
ri->ri_last = ri->ri_first + ri->ri_max;
memset((caddr_t) ri->ri_first, 0, sizeof(ri->ri_first[0]) * ri->ri_max);
ri->ri_last[-1].d_ctl = htole32(LMC_CTL(TULIP_DFLAG_ENDRING, 0, 0));
}