freebsd-dev/sys/dev/lmc/if_lmc.c
Julian Elischer 589f6ed8ce Divorce the kernel binary ABI version number from the message
format version number. (userland programs should not need to be
recompiled when the netgraph kernel internal ABI is changed.

Also fix modules that don;t handle the fact that a caller may not supply
a return message pointer. (benign at the moment because the calling code
checks, but that will change)
2000-12-18 20:03:32 +00:00

1524 lines
39 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.

/*-
* 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);
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);
sprintf(sc->lmc_nodename, "%s%d", NG_LMC_NODE_TYPE, sc->lmc_unit);
if (ng_name_node(sc->lmc_node, sc->lmc_nodename)) {
ng_rmnode(sc->lmc_node);
ng_unref(sc->lmc_node);
return (0);
}
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 *nodep)
{
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, struct ng_mesg *msg,
const char *retaddr, struct ng_mesg **rptr, hook_p lasthook)
{
lmc_softc_t *sc = (lmc_softc_t *) node->private;
struct ng_mesg *resp = NULL;
int error = 0;
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 */
if (rptr)
*rptr = resp;
else if (resp)
FREE(resp, M_NETGRAPH);
free(msg, M_NETGRAPH);
return (error);
}
/*
* get data from another node and transmit it to the line
*/
static int
ng_lmc_rcvdata(hook_p hook, struct mbuf *m, meta_p meta,
struct mbuf **ret_m, meta_p *ret_meta, struct ng_mesg **resp)
{
int s;
int error = 0;
lmc_softc_t * sc = (lmc_softc_t *) hook->node->private;
struct ifqueue *xmitq_p;
/*
* 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_DATA(m, 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);
ng_cutlinks(node);
node->flags &= ~NG_INVALID; /* bounce back to life */
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"