069154d55f
This version is functional and is aproaching solid.. notice I said APROACHING. There are many node types I cannot test I have tested: echo hole ppp socket vjc iface tee bpf async tty The rest compile and "Look" right. More changes to follow. DEBUGGING is enabled in this code to help if people have problems.
1554 lines
40 KiB
C
1554 lines
40 KiB
C
/*-
|
||
* Copyright (c) 1994-1997 Matt Thomas (matt@3am-software.com)
|
||
* Copyright (c) LAN Media Corporation 1998, 1999.
|
||
* Copyright (c) 2000 Stephen Kiernan (sk-ports@vegamuse.org)
|
||
* 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.
|
||
*
|
||
* $FreeBSD$
|
||
* From NetBSD: if_de.c,v 1.56.2.1 1997/10/27 02:13:25 thorpej Exp
|
||
* $Id: if_lmc.c,v 1.9 1999/02/19 15:08:42 explorer Exp $
|
||
*/
|
||
|
||
char lmc_version[] = "BSD 1.1";
|
||
|
||
#include "opt_netgraph.h"
|
||
#include <sys/param.h>
|
||
#include <sys/systm.h>
|
||
#include <sys/mbuf.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/errno.h>
|
||
#include <sys/malloc.h>
|
||
#include <sys/kernel.h>
|
||
|
||
#include <net/if.h>
|
||
#include <sys/syslog.h>
|
||
|
||
#include <vm/vm.h>
|
||
|
||
#include <netgraph/ng_message.h>
|
||
#include <netgraph/ng_parse.h>
|
||
#include <netgraph/netgraph.h>
|
||
|
||
#include <vm/pmap.h>
|
||
#include <pci.h>
|
||
#include <pci/pcivar.h>
|
||
#include <pci/dc21040reg.h>
|
||
#define INCLUDE_PATH_PREFIX "dev/lmc/"
|
||
|
||
/* Intel CPUs should use I/O mapped access. */
|
||
#if defined(__i386__)
|
||
#define LMC_IOMAPPED
|
||
#endif
|
||
|
||
/*
|
||
* This turns on all sort of debugging stuff and make the
|
||
* driver much larger.
|
||
*/
|
||
#ifdef LMC_DEBUG
|
||
#define DP(x) printf x
|
||
#else
|
||
#define DP(x)
|
||
#endif
|
||
|
||
#define LMC_HZ 10
|
||
|
||
#ifndef TULIP_GP_PINSET
|
||
#define TULIP_GP_PINSET 0x00000100L
|
||
#endif
|
||
#ifndef TULIP_BUSMODE_READMULTIPLE
|
||
#define TULIP_BUSMODE_READMULTIPLE 0x00200000L
|
||
#endif
|
||
|
||
/*
|
||
* C sucks
|
||
*/
|
||
typedef struct lmc___softc lmc_softc_t;
|
||
typedef struct lmc___media lmc_media_t;
|
||
typedef struct lmc___ctl lmc_ctl_t;
|
||
|
||
#include "dev/lmc/if_lmcioctl.h"
|
||
#include "dev/lmc/if_lmcvar.h"
|
||
#include "dev/lmc/if_lmc_common.c"
|
||
#include "dev/lmc/if_lmc_media.c"
|
||
|
||
/*
|
||
* This module supports
|
||
* the DEC 21140A pass 2.2 PCI Fast Ethernet Controller.
|
||
*/
|
||
static lmc_intrfunc_t lmc_intr_normal(void *);
|
||
static ifnet_ret_t lmc_ifstart(lmc_softc_t * const sc );
|
||
static ifnet_ret_t lmc_ifstart_one(lmc_softc_t * const sc);
|
||
static struct mbuf *lmc_txput(lmc_softc_t * const sc, struct mbuf *m);
|
||
static void lmc_rx_intr(lmc_softc_t * const sc);
|
||
|
||
static void lmc_watchdog(lmc_softc_t * const sc);
|
||
static void lmc_ifup(lmc_softc_t * const sc);
|
||
static void lmc_ifdown(lmc_softc_t * const sc);
|
||
|
||
#ifdef LMC_DEBUG
|
||
static void ng_lmc_dump_packet(struct mbuf *m);
|
||
#endif /* LMC_DEBUG */
|
||
static void ng_lmc_watchdog_frame(void *arg);
|
||
static void ng_lmc_init(void *ignored);
|
||
|
||
static ng_constructor_t ng_lmc_constructor;
|
||
static ng_rcvmsg_t ng_lmc_rcvmsg;
|
||
static ng_shutdown_t ng_lmc_rmnode;
|
||
static ng_newhook_t ng_lmc_newhook;
|
||
/*static ng_findhook_t ng_lmc_findhook; */
|
||
static ng_connect_t ng_lmc_connect;
|
||
static ng_rcvdata_t ng_lmc_rcvdata;
|
||
static ng_disconnect_t ng_lmc_disconnect;
|
||
|
||
/* Parse type for struct lmc_ctl */
|
||
static const struct ng_parse_fixedarray_info ng_lmc_ctl_cardspec_info = {
|
||
&ng_parse_int32_type,
|
||
7,
|
||
NULL
|
||
};
|
||
|
||
static const struct ng_parse_type ng_lmc_ctl_cardspec_type = {
|
||
&ng_parse_fixedarray_type,
|
||
&ng_lmc_ctl_cardspec_info
|
||
};
|
||
|
||
static const struct ng_parse_struct_info ng_lmc_ctl_type_info = {
|
||
{
|
||
{ "cardtype", &ng_parse_int32_type },
|
||
{ "clock_source", &ng_parse_int32_type },
|
||
{ "clock_rate", &ng_parse_int32_type },
|
||
{ "crc_length", &ng_parse_int32_type },
|
||
{ "cable_length", &ng_parse_int32_type },
|
||
{ "scrambler_onoff", &ng_parse_int32_type },
|
||
{ "cable_type", &ng_parse_int32_type },
|
||
{ "keepalive_onoff", &ng_parse_int32_type },
|
||
{ "ticks", &ng_parse_int32_type },
|
||
{ "cardspec", &ng_lmc_ctl_cardspec_type },
|
||
{ "circuit_type", &ng_parse_int32_type },
|
||
{ NULL },
|
||
}
|
||
};
|
||
|
||
static const struct ng_parse_type ng_lmc_ctl_type = {
|
||
&ng_parse_struct_type,
|
||
&ng_lmc_ctl_type_info
|
||
};
|
||
|
||
|
||
/* List of commands and how to convert arguments to/from ASCII */
|
||
static const struct ng_cmdlist ng_lmc_cmdlist[] = {
|
||
{
|
||
NG_LMC_COOKIE,
|
||
NGM_LMC_GET_CTL,
|
||
"getctl",
|
||
NULL,
|
||
&ng_lmc_ctl_type,
|
||
},
|
||
{
|
||
NG_LMC_COOKIE,
|
||
NGM_LMC_SET_CTL,
|
||
"setctl",
|
||
&ng_lmc_ctl_type,
|
||
NULL
|
||
},
|
||
{ 0 }
|
||
};
|
||
|
||
static struct ng_type typestruct = {
|
||
NG_ABI_VERSION,
|
||
NG_LMC_NODE_TYPE,
|
||
NULL,
|
||
ng_lmc_constructor,
|
||
ng_lmc_rcvmsg,
|
||
ng_lmc_rmnode,
|
||
ng_lmc_newhook,
|
||
NULL,
|
||
ng_lmc_connect,
|
||
ng_lmc_rcvdata,
|
||
ng_lmc_disconnect,
|
||
ng_lmc_cmdlist
|
||
};
|
||
|
||
static int ng_lmc_done_init = 0;
|
||
|
||
|
||
/*
|
||
* Code the read the SROM and MII bit streams (I2C)
|
||
*/
|
||
static 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 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 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, unsigned cmd)
|
||
{
|
||
unsigned 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 unsigned
|
||
lmc_mii_readbits(lmc_softc_t * const sc)
|
||
{
|
||
unsigned data;
|
||
unsigned 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;
|
||
}
|
||
|
||
static unsigned
|
||
lmc_mii_readreg(lmc_softc_t * const sc, unsigned devaddr, unsigned regno)
|
||
{
|
||
unsigned csr = LMC_CSR_READ(sc, csr_srom_mii) & (MII_RD|MII_DOUT|MII_CLK);
|
||
unsigned 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;
|
||
}
|
||
|
||
static void
|
||
lmc_mii_writereg(lmc_softc_t * const sc, unsigned devaddr,
|
||
unsigned regno, unsigned data)
|
||
{
|
||
unsigned 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);
|
||
}
|
||
|
||
static int
|
||
lmc_read_macaddr(lmc_softc_t * const sc)
|
||
{
|
||
lmc_srom_read(sc);
|
||
|
||
bcopy(sc->lmc_rombuf + 20, sc->lmc_enaddr, 6);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Check to make certain there is a signal from the modem, and flicker
|
||
* lights as needed.
|
||
*/
|
||
static void
|
||
lmc_watchdog(lmc_softc_t * const sc)
|
||
{
|
||
int state;
|
||
u_int32_t ostatus;
|
||
u_int32_t link_status;
|
||
u_int32_t ticks;
|
||
|
||
state = 0;
|
||
|
||
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 == 0 && ostatus) {
|
||
printf(LMC_PRINTF_FMT ": physical link down\n",
|
||
LMC_PRINTF_ARGS);
|
||
sc->lmc_flags &= ~LMC_MODEMOK;
|
||
lmc_led_off(sc, LMC_MII16_LED1);
|
||
}
|
||
|
||
/*
|
||
* hardware link is up, but the interface is marked as down.
|
||
* Bring it back up again.
|
||
*/
|
||
if (link_status != 0 && !ostatus) {
|
||
printf(LMC_PRINTF_FMT ": physical link up\n",
|
||
LMC_PRINTF_ARGS);
|
||
if (sc->lmc_flags & LMC_IFUP)
|
||
lmc_ifup(sc);
|
||
sc->lmc_flags |= LMC_MODEMOK;
|
||
lmc_led_on(sc, LMC_MII16_LED1);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* 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);
|
||
|
||
sc->lmc_out_dog = LMC_DOG_HOLDOFF;
|
||
}
|
||
|
||
/*
|
||
* 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)
|
||
{
|
||
untimeout(ng_lmc_watchdog_frame, sc, sc->lmc_handle);
|
||
sc->lmc_running = 0;
|
||
|
||
lmc_dec_reset(sc);
|
||
lmc_reset(sc);
|
||
|
||
sc->lmc_media->set_link_status(sc, 1);
|
||
sc->lmc_media->set_status(sc, NULL);
|
||
|
||
sc->lmc_flags |= LMC_IFUP;
|
||
lmc_led_on(sc, LMC_MII16_LED1);
|
||
|
||
/*
|
||
* select what interrupts we want to get
|
||
*/
|
||
sc->lmc_intrmask |= (TULIP_STS_NORMALINTR
|
||
| TULIP_STS_RXINTR
|
||
| 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);
|
||
|
||
untimeout(ng_lmc_watchdog_frame, sc, sc->lmc_handle);
|
||
sc->lmc_handle = timeout(ng_lmc_watchdog_frame, sc, hz);
|
||
sc->lmc_running = 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)
|
||
{
|
||
untimeout(ng_lmc_watchdog_frame, sc, sc->lmc_handle);
|
||
sc->lmc_running = 0;
|
||
sc->lmc_flags &= ~LMC_IFUP;
|
||
|
||
sc->lmc_media->set_link_status(sc, 0);
|
||
lmc_led_off(sc, LMC_MII16_LED1);
|
||
|
||
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;
|
||
int fillok = 1;
|
||
|
||
sc->lmc_rxtick++;
|
||
|
||
for (;;) {
|
||
tulip_desc_t *eop = ri->ri_nextin;
|
||
int total_len = 0, last_offset = 0;
|
||
struct mbuf *ms = NULL, *me = NULL;
|
||
int accept = 0;
|
||
|
||
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.
|
||
*/
|
||
if ((((volatile tulip_desc_t *) eop)->d_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 (((volatile tulip_desc_t *)eop)->d_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 ((((volatile tulip_desc_t *) eop)->d_status & TULIP_DSTS_RxLASTDESC) == 0) {
|
||
if (++eop == ri->ri_last)
|
||
eop = ri->ri_first;
|
||
if (eop == ri->ri_nextout || ((((volatile tulip_desc_t *) eop)->d_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--) {
|
||
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 = ((eop->d_status >> 16) & 0x7FFF);
|
||
if (sc->ictl.crc_length == 16)
|
||
total_len -= 2;
|
||
else
|
||
total_len -= 4;
|
||
|
||
sc->lmc_inbytes += total_len;
|
||
sc->lmc_inlast = 0;
|
||
|
||
if ((sc->lmc_flags & LMC_RXIGNORE) == 0
|
||
&& ((eop->d_status & LMC_DSTS_ERRSUM) == 0
|
||
)) {
|
||
me->m_len = total_len - last_offset;
|
||
sc->lmc_flags |= LMC_RXACT;
|
||
accept = 1;
|
||
} else {
|
||
sc->lmc_ierrors++;
|
||
if (eop->d_status & TULIP_DSTS_RxOVERFLOW) {
|
||
sc->lmc_dot3stats.dot3StatsInternalMacReceiveErrors++;
|
||
}
|
||
}
|
||
|
||
sc->lmc_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) {
|
||
int error;
|
||
|
||
ms->m_pkthdr.len = total_len;
|
||
ms->m_pkthdr.rcvif = NULL;
|
||
NG_SEND_DATA_ONLY(error, sc->lmc_hook, ms);
|
||
}
|
||
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 {
|
||
ri->ri_nextout->d_length1 = LMC_RX_BUFLEN;
|
||
ri->ri_nextout->d_addr1 = LMC_KVATOPHYS(sc, mtod(ms, caddr_t));
|
||
ri->ri_nextout->d_status = TULIP_DSTS_OWNER;
|
||
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;
|
||
|
||
sc->lmc_txtick++;
|
||
|
||
while (ri->ri_free < ri->ri_max) {
|
||
u_int32_t d_flag;
|
||
if (((volatile tulip_desc_t *) ri->ri_nextin)->d_status & TULIP_DSTS_OWNER)
|
||
break;
|
||
|
||
d_flag = ri->ri_nextin->d_flag;
|
||
if (d_flag & TULIP_DFLAG_TxLASTSEG) {
|
||
const u_int32_t d_status = ri->ri_nextin->d_status;
|
||
_IF_DEQUEUE(&sc->lmc_txq, m);
|
||
if (m != NULL) {
|
||
#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_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;*/
|
||
sc->lmc_out_deficit++;
|
||
}
|
||
/*
|
||
* If nothing left to transmit, disable the timer.
|
||
* Else if progress, reset the timer back to 2 ticks.
|
||
*/
|
||
sc->lmc_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) {
|
||
|
||
*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);
|
||
}
|
||
}
|
||
|
||
static 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;
|
||
tulip_desc_t *eop, *nextout;
|
||
int segcnt, free;
|
||
u_int32_t d_status;
|
||
struct mbuf *m0;
|
||
|
||
#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.
|
||
*/
|
||
again:
|
||
d_status = 0;
|
||
eop = nextout = ri->ri_nextout;
|
||
m0 = m;
|
||
segcnt = 0;
|
||
free = ri->ri_free;
|
||
do {
|
||
int len = m0->m_len;
|
||
caddr_t addr = mtod(m0, caddr_t);
|
||
unsigned clsize = CLBYTES - (((u_long) addr) & (CLBYTES-1));
|
||
|
||
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 = CLBYTES;
|
||
}
|
||
} while ((m0 = m0->m_next) != NULL);
|
||
|
||
|
||
/*
|
||
* 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;
|
||
|
||
/*
|
||
* 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;
|
||
}
|
||
|
||
/*
|
||
* Mark the last and first segments, indicate we want a transmit
|
||
* complete interrupt, and tell it to transmit!
|
||
*/
|
||
eop->d_flag |= TULIP_DFLAG_TxLASTSEG | TULIP_DFLAG_TxWANTINTR;
|
||
|
||
/*
|
||
* 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.
|
||
*/
|
||
ri->ri_nextout->d_flag |= TULIP_DFLAG_TxFIRSTSEG;
|
||
ri->ri_nextout->d_status = TULIP_DSTS_OWNER;
|
||
|
||
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_xmit_busy = 0;
|
||
sc->lmc_out_dog = 0;
|
||
|
||
/*
|
||
* 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:
|
||
|
||
return m;
|
||
}
|
||
|
||
|
||
/*
|
||
* These routines gets called at device spl
|
||
*/
|
||
|
||
static ifnet_ret_t
|
||
lmc_ifstart(lmc_softc_t * const sc)
|
||
{
|
||
struct mbuf *m;
|
||
|
||
if (sc->lmc_flags & LMC_IFUP) {
|
||
sc->lmc_xmit_busy = 1;
|
||
for(;;) {
|
||
struct ifqueue *q = &sc->lmc_xmitq_hipri;
|
||
IF_DEQUEUE(q, m);
|
||
if (m == NULL) {
|
||
q = &sc->lmc_xmitq;
|
||
IF_DEQUEUE(q, m);
|
||
}
|
||
if (m) {
|
||
sc->lmc_outbytes = m->m_pkthdr.len;
|
||
sc->lmc_opackets++;
|
||
if ((m = lmc_txput(sc, m)) != NULL) {
|
||
IF_PREPEND(q, m);
|
||
printf(LMC_PRINTF_FMT
|
||
": lmc_txput failed\n",
|
||
LMC_PRINTF_ARGS);
|
||
break;
|
||
}
|
||
LMC_CSR_WRITE(sc, csr_txpoll, 1);
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
static ifnet_ret_t
|
||
lmc_ifstart_one(lmc_softc_t * const sc)
|
||
{
|
||
struct mbuf *m;
|
||
|
||
if ((sc->lmc_flags & LMC_IFUP)) {
|
||
struct ifqueue *q = &sc->lmc_xmitq_hipri;
|
||
IF_DEQUEUE(q, m);
|
||
if (m == NULL) {
|
||
q = &sc->lmc_xmitq;
|
||
IF_DEQUEUE(q, m);
|
||
}
|
||
if (m) {
|
||
sc->lmc_outbytes += m->m_pkthdr.len;
|
||
sc->lmc_opackets++;
|
||
if ((m = lmc_txput(sc, m)) != NULL) {
|
||
IF_PREPEND(q, m);
|
||
}
|
||
LMC_CSR_WRITE(sc, csr_txpoll, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Set up the OS interface magic and attach to the operating system
|
||
* network services.
|
||
*/
|
||
static int
|
||
lmc_attach(lmc_softc_t * const sc)
|
||
{
|
||
/*
|
||
* we have found a node, make sure our 'type' is availabe.
|
||
*/
|
||
if (ng_lmc_done_init == 0) ng_lmc_init(NULL);
|
||
if (ng_make_node_common(&typestruct, &sc->lmc_node) != 0)
|
||
return (0);
|
||
sprintf(sc->lmc_nodename, "%s%d", NG_LMC_NODE_TYPE, sc->lmc_unit);
|
||
if (ng_name_node(sc->lmc_node, sc->lmc_nodename)) {
|
||
ng_unref(sc->lmc_node); /* make it go away again */
|
||
return (0);
|
||
}
|
||
sc->lmc_node->private = sc;
|
||
callout_handle_init(&sc->lmc_handle);
|
||
sc->lmc_xmitq.ifq_maxlen = IFQ_MAXLEN;
|
||
sc->lmc_xmitq_hipri.ifq_maxlen = IFQ_MAXLEN;
|
||
mtx_init(&sc->lmc_xmitq.ifq_mtx, "lmc_xmitq", MTX_DEF);
|
||
mtx_init(&sc->lmc_xmitq_hipri.ifq_mtx, "lmc_xmitq_hipri", MTX_DEF);
|
||
sc->lmc_running = 0;
|
||
|
||
/*
|
||
* turn off those LEDs...
|
||
*/
|
||
sc->lmc_miireg16 |= LMC_MII16_LED_ALL;
|
||
lmc_led_on(sc, LMC_MII16_LED0);
|
||
|
||
return 1;
|
||
}
|
||
|
||
static void
|
||
lmc_initring(lmc_softc_t * const sc, lmc_ringinfo_t * const ri,
|
||
tulip_desc_t *descs, int ndescs)
|
||
{
|
||
ri->ri_max = ndescs;
|
||
ri->ri_first = descs;
|
||
ri->ri_last = ri->ri_first + ri->ri_max;
|
||
bzero((caddr_t) ri->ri_first, sizeof(ri->ri_first[0]) * ri->ri_max);
|
||
ri->ri_last[-1].d_flag = TULIP_DFLAG_ENDRING;
|
||
}
|
||
|
||
|
||
|
||
#ifdef LMC_DEBUG
|
||
static void
|
||
ng_lmc_dump_packet(struct mbuf *m)
|
||
{
|
||
int i;
|
||
|
||
printf("mbuf: %d bytes, %s packet\n", m->m_len,
|
||
(m->m_type == MT_DATA)?"data":"other");
|
||
|
||
for (i=0; i < m->m_len; i++) {
|
||
if( (i % 8) == 0 ) {
|
||
if( i ) printf("\n");
|
||
printf("\t");
|
||
}
|
||
else
|
||
printf(" ");
|
||
printf( "0x%02x", m->m_dat[i] );
|
||
}
|
||
printf("\n");
|
||
}
|
||
#endif /* LMC_DEBUG */
|
||
|
||
/* Device timeout/watchdog routine */
|
||
static void
|
||
ng_lmc_watchdog_frame(void *arg)
|
||
{
|
||
lmc_softc_t * sc = (lmc_softc_t *) arg;
|
||
int s;
|
||
int speed;
|
||
|
||
if(sc->lmc_running == 0)
|
||
return; /* if we are not running let timeouts die */
|
||
/*
|
||
* calculate the apparent throughputs
|
||
* XXX a real hack
|
||
*/
|
||
s = splimp();
|
||
speed = sc->lmc_inbytes - sc->lmc_lastinbytes;
|
||
sc->lmc_lastinbytes = sc->lmc_inbytes;
|
||
if ( sc->lmc_inrate < speed )
|
||
sc->lmc_inrate = speed;
|
||
speed = sc->lmc_outbytes - sc->lmc_lastoutbytes;
|
||
sc->lmc_lastoutbytes = sc->lmc_outbytes;
|
||
if ( sc->lmc_outrate < speed )
|
||
sc->lmc_outrate = speed;
|
||
sc->lmc_inlast++;
|
||
splx(s);
|
||
|
||
if ((sc->lmc_inlast > LMC_QUITE_A_WHILE)
|
||
&& (sc->lmc_out_deficit > LMC_LOTS_OF_PACKETS)) {
|
||
log(LOG_ERR, "%s%d: No response from remote end\n",
|
||
sc->lmc_name, sc->lmc_unit);
|
||
s = splimp();
|
||
lmc_ifdown(sc);
|
||
lmc_ifup(sc);
|
||
sc->lmc_inlast = sc->lmc_out_deficit = 0;
|
||
splx(s);
|
||
} else if (sc->lmc_xmit_busy) {
|
||
if (sc->lmc_out_dog == 0) {
|
||
log(LOG_ERR, "ar%d: Transmit failure.. no clock?\n",
|
||
sc->lmc_unit);
|
||
s = splimp();
|
||
lmc_watchdog(sc);
|
||
#if 0
|
||
lmc_ifdown(sc);
|
||
lmc_ifup(sc);
|
||
#endif
|
||
splx(s);
|
||
sc->lmc_inlast = sc->lmc_out_deficit = 0;
|
||
} else {
|
||
sc->lmc_out_dog--;
|
||
}
|
||
}
|
||
lmc_watchdog(sc);
|
||
sc->lmc_handle = timeout(ng_lmc_watchdog_frame, sc, hz);
|
||
}
|
||
|
||
/***********************************************************************
|
||
* This section contains the methods for the Netgraph interface
|
||
***********************************************************************/
|
||
/*
|
||
* It is not possible or allowable to create a node of this type.
|
||
* If the hardware exists, it will already have created it.
|
||
*/
|
||
static int
|
||
ng_lmc_constructor(node_p node)
|
||
{
|
||
return (EINVAL);
|
||
}
|
||
|
||
/*
|
||
* give our ok for a hook to be added...
|
||
* If we are not running this should kick the device into life.
|
||
* We allow hooks called "control", "rawdata", and "debug".
|
||
* The hook's private info points to our stash of info about that
|
||
* device.
|
||
*/
|
||
static int
|
||
ng_lmc_newhook(node_p node, hook_p hook, const char *name)
|
||
{
|
||
lmc_softc_t * sc = (lmc_softc_t *) node->private;
|
||
|
||
/*
|
||
* check if it's our friend the debug hook
|
||
*/
|
||
if (strcmp(name, NG_LMC_HOOK_DEBUG) == 0) {
|
||
hook->private = NULL; /* paranoid */
|
||
sc->lmc_debug_hook = hook;
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* Check for raw mode hook.
|
||
*/
|
||
if (strcmp(name, NG_LMC_HOOK_RAW) != 0) {
|
||
return (EINVAL);
|
||
}
|
||
hook->private = sc;
|
||
sc->lmc_hook = hook;
|
||
sc->lmc_datahooks++;
|
||
lmc_ifup(sc);
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* incoming messages.
|
||
* Just respond to the generic TEXT_STATUS message
|
||
*/
|
||
static int
|
||
ng_lmc_rcvmsg(node_p node, item_p item, hook_p lasthook)
|
||
{
|
||
lmc_softc_t *sc = (lmc_softc_t *) node->private;
|
||
struct ng_mesg *resp = NULL;
|
||
int error = 0;
|
||
struct ng_mesg *msg;
|
||
|
||
NGI_GET_MSG(item, msg);
|
||
switch (msg->header.typecookie) {
|
||
case NG_LMC_COOKIE:
|
||
switch (msg->header.cmd) {
|
||
case NGM_LMC_GET_CTL:
|
||
{
|
||
lmc_ctl_t *ctl;
|
||
|
||
NG_MKRESPONSE(resp, msg, sizeof(*ctl), M_NOWAIT);
|
||
if (!resp) {
|
||
error = ENOMEM;
|
||
break;
|
||
}
|
||
ctl = (lmc_ctl_t *) resp->data;
|
||
memcpy( ctl, &sc->ictl, sizeof(*ctl) );
|
||
break;
|
||
}
|
||
case NGM_LMC_SET_CTL:
|
||
{
|
||
lmc_ctl_t *ctl;
|
||
|
||
if (msg->header.arglen != sizeof(*ctl)) {
|
||
error = EINVAL;
|
||
break;
|
||
}
|
||
|
||
ctl = (lmc_ctl_t *) msg->data;
|
||
sc->lmc_media->set_status(sc, ctl);
|
||
break;
|
||
}
|
||
default:
|
||
error = EINVAL; /* unknown command */
|
||
break;
|
||
}
|
||
break;
|
||
case NGM_GENERIC_COOKIE:
|
||
switch(msg->header.cmd) {
|
||
case NGM_TEXT_STATUS: {
|
||
char *arg;
|
||
int pos = 0;
|
||
|
||
int resplen = sizeof(struct ng_mesg) + 512;
|
||
NG_MKRESPONSE(resp, msg, resplen, M_NOWAIT);
|
||
if (resp == NULL) {
|
||
error = ENOMEM;
|
||
break;
|
||
}
|
||
arg = (resp)->data;
|
||
|
||
/*
|
||
* Put in the throughput information.
|
||
*/
|
||
pos = sprintf(arg, "%ld bytes in, %ld bytes out\n"
|
||
"highest rate seen: %ld B/S in, "
|
||
"%ld B/S out\n",
|
||
sc->lmc_inbytes, sc->lmc_outbytes,
|
||
sc->lmc_inrate, sc->lmc_outrate);
|
||
pos += sprintf(arg + pos, "%ld output errors\n",
|
||
sc->lmc_oerrors);
|
||
pos += sprintf(arg + pos, "%ld input errors\n",
|
||
sc->lmc_ierrors);
|
||
|
||
resp->header.arglen = pos + 1;
|
||
break;
|
||
}
|
||
default:
|
||
error = EINVAL;
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
error = EINVAL;
|
||
break;
|
||
}
|
||
|
||
/* Take care of synchronous response, if any */
|
||
NG_RESPOND_MSG(error, node, item, resp);
|
||
NG_FREE_MSG(msg);
|
||
return (error);
|
||
}
|
||
|
||
/*
|
||
* get data from another node and transmit it to the line
|
||
*/
|
||
static int
|
||
ng_lmc_rcvdata(hook_p hook, item_p item)
|
||
{
|
||
int s;
|
||
int error = 0;
|
||
lmc_softc_t * sc = (lmc_softc_t *) hook->node->private;
|
||
struct ifqueue *xmitq_p;
|
||
struct mbuf *m;
|
||
meta_p meta;
|
||
|
||
/* Unpack all the data components */
|
||
NGI_GET_M(item, m);
|
||
NGI_GET_META(item, meta);
|
||
NG_FREE_ITEM(item);
|
||
|
||
/*
|
||
* data doesn't come in from just anywhere (e.g control hook)
|
||
*/
|
||
if ( hook->private == NULL) {
|
||
error = ENETDOWN;
|
||
goto bad;
|
||
}
|
||
|
||
/*
|
||
* Now queue the data for when it can be sent
|
||
*/
|
||
if (meta && meta->priority > 0) {
|
||
xmitq_p = (&sc->lmc_xmitq_hipri);
|
||
} else {
|
||
xmitq_p = (&sc->lmc_xmitq);
|
||
}
|
||
s = splimp();
|
||
IF_LOCK(xmitq_p);
|
||
if (_IF_QFULL(xmitq_p)) {
|
||
_IF_DROP(xmitq_p);
|
||
IF_UNLOCK(xmitq_p);
|
||
splx(s);
|
||
error = ENOBUFS;
|
||
goto bad;
|
||
}
|
||
_IF_ENQUEUE(xmitq_p, m);
|
||
IF_UNLOCK(xmitq_p);
|
||
lmc_ifstart_one(sc);
|
||
splx(s);
|
||
return (0);
|
||
|
||
bad:
|
||
/*
|
||
* It was an error case.
|
||
* check if we need to free the mbuf, and then return the error
|
||
*/
|
||
NG_FREE_M(m);
|
||
NG_FREE_META(meta);
|
||
return (error);
|
||
}
|
||
|
||
/*
|
||
* do local shutdown processing..
|
||
* this node will refuse to go away, unless the hardware says to..
|
||
* don't unref the node, or remove our name. just clear our links up.
|
||
*/
|
||
static int
|
||
ng_lmc_rmnode(node_p node)
|
||
{
|
||
lmc_softc_t * sc = (lmc_softc_t *) node->private;
|
||
|
||
lmc_ifdown(sc);
|
||
|
||
/*
|
||
* Get rid of the old node.
|
||
*/
|
||
node->flags |= NG_INVALID;
|
||
node->private = NULL;
|
||
ng_unref(node);
|
||
|
||
/*
|
||
* Create a new node. This is basically what a device
|
||
* driver would do in the attach routine. So let's just do that..
|
||
* The node is dead, long live the node!
|
||
*/
|
||
if (ng_make_node_common(&typestruct, &sc->lmc_node) != 0)
|
||
return (0);
|
||
sprintf(sc->lmc_nodename, "%s%d", NG_LMC_NODE_TYPE, sc->lmc_unit);
|
||
if (ng_name_node(sc->lmc_node, sc->lmc_nodename)) {
|
||
sc->lmc_node = NULL; /* to be sure */
|
||
ng_unref(sc->lmc_node); /* make it go away */
|
||
return (0);
|
||
}
|
||
sc->lmc_node->private = sc;
|
||
callout_handle_init(&sc->lmc_handle);
|
||
sc->lmc_running = 0;
|
||
/*
|
||
* turn off those LEDs...
|
||
*/
|
||
sc->lmc_miireg16 |= LMC_MII16_LED_ALL;
|
||
lmc_led_on(sc, LMC_MII16_LED0);
|
||
return (0);
|
||
}
|
||
/* already linked */
|
||
static int
|
||
ng_lmc_connect(hook_p hook)
|
||
{
|
||
/* We are probably not at splnet.. force outward queueing */
|
||
hook->peer->flags |= HK_QUEUE;
|
||
/* be really amiable and just say "YUP that's OK by me! " */
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* notify on hook disconnection (destruction)
|
||
*
|
||
* For this type, removal of the last link resets tries to destroy the node.
|
||
* As the device still exists, the shutdown method will not actually
|
||
* destroy the node, but reset the device and leave it 'fresh' :)
|
||
*
|
||
* The node removal code will remove all references except that owned by the
|
||
* driver.
|
||
*/
|
||
static int
|
||
ng_lmc_disconnect(hook_p hook)
|
||
{
|
||
lmc_softc_t * sc = (lmc_softc_t *) hook->node->private;
|
||
int s;
|
||
/*
|
||
* If it's the data hook, then free resources etc.
|
||
*/
|
||
if (hook->private) {
|
||
s = splimp();
|
||
sc->lmc_datahooks--;
|
||
if (sc->lmc_datahooks == 0)
|
||
lmc_ifdown(sc);
|
||
splx(s);
|
||
} else {
|
||
sc->lmc_debug_hook = NULL;
|
||
}
|
||
return (0);
|
||
}
|
||
|
||
/*
|
||
* called during bootup
|
||
* or LKM loading to put this type into the list of known modules
|
||
*/
|
||
static void
|
||
ng_lmc_init(void *ignored)
|
||
{
|
||
if (ng_newtype(&typestruct))
|
||
printf("ng_lmc install failed\n");
|
||
ng_lmc_done_init = 1;
|
||
}
|
||
|
||
/*
|
||
* This is the PCI configuration support.
|
||
*/
|
||
#define PCI_CFID 0x00 /* Configuration ID */
|
||
#define PCI_CFCS 0x04 /* Configurtion Command/Status */
|
||
#define PCI_CFRV 0x08 /* Configuration Revision */
|
||
#define PCI_CFLT 0x0c /* Configuration Latency Timer */
|
||
#define PCI_CBIO 0x10 /* Configuration Base IO Address */
|
||
#define PCI_CBMA 0x14 /* Configuration Base Memory Address */
|
||
#define PCI_SSID 0x2c /* subsystem config register */
|
||
#define PCI_CFIT 0x3c /* Configuration Interrupt */
|
||
#define PCI_CFDA 0x40 /* Configuration Driver Area */
|
||
|
||
|
||
|
||
#include "dev/lmc/if_lmc_fbsd3.c"
|