#include "bpfilter.h"
#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/syslog.h>
#include <sys/device.h>
#include <net/if.h>
#include <net/if_media.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <machine/bus.h>
#include <dev/ic/i82596reg.h>
#include <dev/ic/i82596var.h>
static char *padbuf;
void i82596_reset(struct ie_softc *, int);
void i82596_watchdog(struct ifnet *);
int i82596_init(struct ie_softc *);
int i82596_ioctl(struct ifnet *, u_long, caddr_t);
void i82596_start(struct ifnet *);
int i82596_rint(struct ie_softc *, int);
int i82596_tint(struct ie_softc *, int);
int i82596_mediachange(struct ifnet *);
void i82596_mediastatus(struct ifnet *, struct ifmediareq *);
int i82596_readframe(struct ie_softc *, int);
int i82596_get_rbd_list(struct ie_softc *,
u_int16_t *, u_int16_t *, int *);
void i82596_release_rbd_list(struct ie_softc *, u_int16_t, u_int16_t);
int i82596_drop_frames(struct ie_softc *);
int i82596_chk_rx_ring(struct ie_softc *);
void i82596_start_transceiver(struct ie_softc *);
void i82596_stop(struct ie_softc *);
void i82596_xmit(struct ie_softc *);
void i82596_setup_bufs(struct ie_softc *);
void i82596_simple_command(struct ie_softc *, int, int);
int ie_cfg_setup(struct ie_softc *, int, int, int);
int ie_ia_setup(struct ie_softc *, int);
void ie_run_tdr(struct ie_softc *, int);
int ie_mc_setup(struct ie_softc *, int);
void ie_mc_reset(struct ie_softc *);
int i82596_cmd_wait(struct ie_softc *);
#ifdef I82596_DEBUG
void print_rbd(struct ie_softc *, int);
#endif
struct cfdriver ie_cd = {
NULL, "ie", DV_IFNET
};
int
i82596_probe(struct ie_softc *sc)
{
int i;
sc->scp = sc->sc_msize - IE_SCP_SZ;
sc->iscp = 0;
sc->scb = 32;
(sc->ie_bus_write16)(sc, IE_ISCP_BUSY(sc->iscp), 1);
(sc->ie_bus_write16)(sc, IE_ISCP_SCB(sc->iscp), sc->scb);
(sc->ie_bus_write24)(sc, IE_ISCP_BASE(sc->iscp), sc->sc_maddr);
(sc->ie_bus_write24)(sc, IE_SCP_ISCP(sc->scp), sc->sc_maddr);
(sc->ie_bus_write16)(sc, IE_SCP_BUS_USE(sc->scp), sc->sysbus);
(sc->hwreset)(sc, IE_CARD_RESET);
if ((sc->ie_bus_read16)(sc, IE_ISCP_BUSY(sc->iscp))) {
#ifdef I82596_DEBUG
printf("%s: ISCP set failed\n", sc->sc_dev.dv_xname);
#endif
return 0;
}
if (sc->port) {
(sc->ie_bus_write24)(sc, sc->scp, 0);
(sc->ie_bus_write24)(sc, IE_SCP_TEST(sc->scp), -1);
(sc->port)(sc, IE_PORT_TEST);
for (i = 9000; i-- &&
(sc->ie_bus_read16)(sc, IE_SCP_TEST(sc->scp));
DELAY(100))
;
}
return 1;
}
void
i82596_attach(struct ie_softc *sc, const char *name, u_int8_t *etheraddr,
uint64_t *media, int nmedia, uint64_t defmedia)
{
int i;
struct ifnet *ifp = &sc->sc_arpcom.ac_if;
(sc->ie_bus_write16)(sc, IE_ISCP_BUSY(sc->iscp), 1);
(sc->ie_bus_write16)(sc, IE_ISCP_SCB(sc->iscp), sc->scb);
(sc->ie_bus_write24)(sc, IE_ISCP_BASE(sc->iscp), sc->sc_maddr);
(sc->ie_bus_write24)(sc, IE_SCP_ISCP(sc->scp), sc->sc_maddr +sc->iscp);
(sc->ie_bus_write16)(sc, IE_SCP_BUS_USE(sc->scp), sc->sysbus);
(sc->hwreset)(sc, IE_CARD_RESET);
bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_start = i82596_start;
ifp->if_ioctl = i82596_ioctl;
ifp->if_watchdog = i82596_watchdog;
ifp->if_flags =
#ifdef I82596_DEBUG
IFF_DEBUG |
#endif
IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifmedia_init(&sc->sc_media, 0, i82596_mediachange, i82596_mediastatus);
if (media != NULL) {
for (i = 0; i < nmedia; i++)
ifmedia_add(&sc->sc_media, media[i], 0, NULL);
ifmedia_set(&sc->sc_media, defmedia);
} else {
ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_MANUAL, 0, NULL);
ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_MANUAL);
}
if (padbuf == NULL) {
padbuf = malloc(ETHER_MIN_LEN - ETHER_CRC_LEN, M_DEVBUF,
M_NOWAIT | M_ZERO);
if (padbuf == NULL) {
printf("%s: can't allocate pad buffer\n",
sc->sc_dev.dv_xname);
return;
}
}
if_attach(ifp);
ether_ifattach(ifp);
printf(" %s v%d.%d, address %s\n", name, sc->sc_vers / 10,
sc->sc_vers % 10, ether_sprintf(etheraddr));
}
void
i82596_watchdog(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
log(LOG_ERR, "%s: device timeout\n", sc->sc_dev.dv_xname);
++ifp->if_oerrors;
i82596_reset(sc, 1);
}
int
i82596_cmd_wait(struct ie_softc *sc)
{
int i, off;
for (i = 180000; i--; DELAY(5)) {
off = IE_SCB_CMD(sc->scb);
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_READ);
if ((sc->ie_bus_read16)(sc, off) == 0) {
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_CMDS)
printf("%s: cmd_wait after %d usec\n",
sc->sc_dev.dv_xname, (180000 - i) * 5);
#endif
return (0);
}
}
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_CMDS)
printf("i82596_cmd_wait: timo(%ssync): scb status: %b\n",
sc->async_cmd_inprogress? "a" : "",
sc->ie_bus_read16(sc, IE_SCB_STATUS(sc->scb)),
IE_STAT_BITS);
#endif
return (1);
}
int
i82596_start_cmd(struct ie_softc *sc, int cmd, int iecmdbuf, int mask,
int async)
{
int i, off;
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_CMDS)
printf("start_cmd: %p, %x, %x, %b, %ssync\n",
sc, cmd, iecmdbuf, mask, IE_STAT_BITS, async?"a":"");
#endif
if (sc->async_cmd_inprogress != 0) {
if (i82596_cmd_wait(sc) != 0)
return (1);
sc->async_cmd_inprogress = 0;
}
off = IE_SCB_CMD(sc->scb);
(sc->ie_bus_write16)(sc, off, cmd);
bus_space_barrier(sc->bt, sc->bh, off, 2, BUS_SPACE_BARRIER_WRITE);
(sc->chan_attn)(sc);
if (async) {
sc->async_cmd_inprogress = 1;
return (0);
}
if (IE_ACTION_COMMAND(cmd) && iecmdbuf) {
int status;
for (i = 73800; i--; DELAY(5)) {
off = IE_CMD_COMMON_STATUS(iecmdbuf);
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_READ);
status = (sc->ie_bus_read16)(sc, off);
if (status & mask) {
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_CMDS)
printf("%s: cmd status %b\n",
sc->sc_dev.dv_xname,
status, IE_STAT_BITS);
#endif
return (0);
}
}
} else {
return (i82596_cmd_wait(sc));
}
return (1);
}
static __inline void
i82596_count_errors(struct ie_softc *sc)
{
int scb = sc->scb;
sc->sc_arpcom.ac_if.if_ierrors +=
sc->ie_bus_read16(sc, IE_SCB_ERRCRC(scb)) +
sc->ie_bus_read16(sc, IE_SCB_ERRALN(scb)) +
sc->ie_bus_read16(sc, IE_SCB_ERRRES(scb)) +
sc->ie_bus_read16(sc, IE_SCB_ERROVR(scb));
sc->ie_bus_write16(sc, IE_SCB_ERRCRC(scb), 0);
sc->ie_bus_write16(sc, IE_SCB_ERRALN(scb), 0);
sc->ie_bus_write16(sc, IE_SCB_ERRRES(scb), 0);
sc->ie_bus_write16(sc, IE_SCB_ERROVR(scb), 0);
}
static __inline void
i82596_rx_errors(struct ie_softc *sc, int fn, int status)
{
log(LOG_ERR, "%s: rx error (frame# %d): %b\n", sc->sc_dev.dv_xname, fn,
status, IE_FD_STATUSBITS);
}
int
i82596_intr(void *v)
{
register struct ie_softc *sc = v;
register u_int status;
register int off;
off = IE_SCB_STATUS(sc->scb);
bus_space_barrier(sc->bt, sc->bh, off, 2, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off) ;
if ((status & IE_ST_WHENCE) == 0) {
if (sc->intrhook)
(sc->intrhook)(sc, IE_INTR_EXIT);
return (0);
}
loop:
i82596_start_cmd(sc, status & IE_ST_WHENCE, 0, 0, 1);
if (status & (IE_ST_FR | IE_ST_RNR)) {
if (sc->intrhook)
(sc->intrhook)(sc, IE_INTR_ENRCV);
if (i82596_rint(sc, status) != 0)
goto reset;
}
if (status & IE_ST_CX) {
if (sc->intrhook)
(sc->intrhook)(sc, IE_INTR_ENSND);
if (i82596_tint(sc, status) != 0)
goto reset;
}
#ifdef I82596_DEBUG
if ((status & IE_ST_CNA) && (sc->sc_debug & IED_CNA))
printf("%s: cna; status=%b\n", sc->sc_dev.dv_xname,
status, IE_ST_BITS);
#endif
if (sc->intrhook)
(sc->intrhook)(sc, IE_INTR_LOOP);
if (i82596_cmd_wait(sc))
goto reset;
bus_space_barrier(sc->bt, sc->bh, off, 2, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off);
if ((status & IE_ST_WHENCE) != 0)
goto loop;
out:
if (sc->intrhook)
(sc->intrhook)(sc, IE_INTR_EXIT);
return (1);
reset:
i82596_cmd_wait(sc);
i82596_reset(sc, 1);
goto out;
}
int
i82596_rint(struct ie_softc *sc, int scbstatus)
{
static int timesthru = 1024;
register int i, status, off;
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_RINT)
printf("%s: rint: status %b\n",
sc->sc_dev.dv_xname, scbstatus, IE_ST_BITS);
#endif
for (;;) {
register int drop = 0;
i = sc->rfhead;
off = IE_RFRAME_STATUS(sc->rframes, i);
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off);
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_RINT)
printf("%s: rint: frame(%d) status %b\n",
sc->sc_dev.dv_xname, i, status, IE_ST_BITS);
#endif
if ((status & IE_FD_COMPLETE) == 0) {
if ((status & IE_FD_OK) != 0) {
printf("%s: rint: weird: ",
sc->sc_dev.dv_xname);
i82596_rx_errors(sc, i, status);
break;
}
if (--timesthru == 0) {
i82596_count_errors(sc);
timesthru = 1024;
}
break;
} else if ((status & IE_FD_OK) == 0) {
i82596_rx_errors(sc, i, status);
drop = 1;
}
#ifdef I82596_DEBUG
if ((status & IE_FD_BUSY) != 0)
printf("%s: rint: frame(%d) busy; status=%x\n",
sc->sc_dev.dv_xname, i, status, IE_ST_BITS);
#endif
sc->ie_bus_write16(sc, off, 0);
off = IE_RFRAME_LAST(sc->rframes, i);
sc->ie_bus_write16(sc, off, IE_FD_EOL|IE_FD_SUSP);
off = IE_RFRAME_BUFDESC(sc->rframes, i);
sc->ie_bus_write16(sc, off, 0xffff);
off = IE_RFRAME_LAST(sc->rframes, sc->rftail);
sc->ie_bus_write16(sc, off, 0);
if (++sc->rftail == sc->nframes)
sc->rftail = 0;
if (++sc->rfhead == sc->nframes)
sc->rfhead = 0;
if (drop) {
i82596_drop_frames(sc);
if ((status & IE_FD_RNR) != 0)
sc->rnr_expect = 1;
sc->sc_arpcom.ac_if.if_ierrors++;
} else if (i82596_readframe(sc, i) != 0)
return (1);
}
if ((scbstatus & IE_ST_RNR) != 0) {
if ((scbstatus & IE_RUS_SUSPEND) != 0) {
printf("RINT: SUSPENDED; scbstatus=%b\n",
scbstatus, IE_ST_BITS);
if (i82596_start_cmd(sc, IE_RUC_RESUME, 0, 0, 0) == 0)
return (0);
printf("%s: RU RESUME command timed out\n",
sc->sc_dev.dv_xname);
return (1);
}
if (sc->rnr_expect != 0) {
i82596_start_transceiver(sc);
sc->rnr_expect = 0;
return (0);
} else if ((scbstatus & IE_RUS_NOSPACE) != 0) {
if (i82596_chk_rx_ring(sc) != 0)
return (1);
i82596_start_transceiver(sc);
sc->sc_arpcom.ac_if.if_ierrors++;
return (0);
} else
printf("%s: receiver not ready; scbstatus=%b\n",
sc->sc_dev.dv_xname, scbstatus, IE_ST_BITS);
sc->sc_arpcom.ac_if.if_ierrors++;
return (1);
}
return (0);
}
int
i82596_tint(struct ie_softc *sc, int scbstatus)
{
register struct ifnet *ifp = &sc->sc_arpcom.ac_if;
register int off, status;
ifp->if_timer = 0;
ifq_clr_oactive(&ifp->if_snd);
#ifdef I82596_DEBUG
if (sc->xmit_busy <= 0) {
printf("%s: i82596_tint: WEIRD:"
"xmit_busy=%d, xctail=%d, xchead=%d\n",
sc->sc_dev.dv_xname,
sc->xmit_busy, sc->xctail, sc->xchead);
return (0);
}
#endif
off = IE_CMD_XMIT_STATUS(sc->xmit_cmds, sc->xctail);
status = sc->ie_bus_read16(sc, off);
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_TINT)
printf("%s: tint: SCB status %b; xmit status %b\n",
sc->sc_dev.dv_xname, scbstatus, IE_ST_BITS,
status, IE_XS_BITS);
#endif
if ((status & (IE_STAT_COMPL|IE_STAT_BUSY)) == IE_STAT_BUSY) {
printf("%s: i82596_tint: command still busy;"
"status=%b; tail=%d\n", sc->sc_dev.dv_xname,
status, IE_XS_BITS, sc->xctail);
printf("iestatus = %b\n", scbstatus, IE_ST_BITS);
}
if (status & IE_STAT_OK) {
ifp->if_collisions += (status & IE_XS_MAXCOLL);
} else {
ifp->if_oerrors++;
#ifdef I82596_DEBUG
if (status & IE_STAT_ABORT)
printf("%s: send aborted\n", sc->sc_dev.dv_xname);
else if (status & IE_XS_NOCARRIER)
printf("%s: no carrier\n", sc->sc_dev.dv_xname);
else if (status & IE_XS_LOSTCTS)
printf("%s: lost CTS\n", sc->sc_dev.dv_xname);
else if (status & IE_XS_UNDERRUN)
printf("%s: DMA underrun\n", sc->sc_dev.dv_xname);
else
#endif
if (status & IE_XS_EXCMAX) {
#ifdef I82596_DEBUG
printf("%s: too many collisions\n",
sc->sc_dev.dv_xname);
#endif
sc->sc_arpcom.ac_if.if_collisions += 16;
}
}
if (sc->want_mcsetup) {
ie_mc_setup(sc, IE_XBUF_ADDR(sc, sc->xctail));
sc->want_mcsetup = 0;
}
sc->xmit_busy--;
sc->xctail = (sc->xctail + 1) % NTXBUF;
if (sc->xmit_busy > 0)
i82596_xmit(sc);
i82596_start(ifp);
return (0);
}
int
i82596_get_rbd_list(struct ie_softc *sc, u_int16_t *start, u_int16_t *end,
int *pktlen)
{
int off, rbbase = sc->rbds;
int rbindex, count = 0;
int plen = 0;
int rbdstatus;
*start = rbindex = sc->rbhead;
do {
off = IE_RBD_STATUS(rbbase, rbindex);
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_READ);
rbdstatus = sc->ie_bus_read16(sc, off);
if ((rbdstatus & IE_RBD_USED) == 0) {
#ifdef I82596_DEBUG
print_rbd(sc, rbindex);
#endif
log(LOG_ERR,
"%s: receive descriptors out of sync at %d\n",
sc->sc_dev.dv_xname, rbindex);
return (0);
}
plen += (rbdstatus & IE_RBD_CNTMASK);
if (++rbindex == sc->nrxbuf)
rbindex = 0;
++count;
} while ((rbdstatus & IE_RBD_LAST) == 0);
*end = rbindex;
*pktlen = plen;
return (count);
}
void
i82596_release_rbd_list(struct ie_softc *sc, u_int16_t start, u_int16_t end)
{
register int off, rbbase = sc->rbds;
register int rbindex = start;
do {
off = IE_RBD_STATUS(rbbase, rbindex);
sc->ie_bus_write16(sc, off, 0);
if (++rbindex == sc->nrxbuf)
rbindex = 0;
} while (rbindex != end);
rbindex = ((rbindex == 0) ? sc->nrxbuf : rbindex) - 1;
off = IE_RBD_BUFLEN(rbbase, rbindex);
sc->ie_bus_write16(sc, off, IE_RBUF_SIZE|IE_RBD_EOL);
off = IE_RBD_BUFLEN(rbbase, sc->rbtail);
sc->ie_bus_write16(sc, off, IE_RBUF_SIZE);
sc->rbhead = end;
sc->rbtail = rbindex;
}
int
i82596_drop_frames(struct ie_softc *sc)
{
u_int16_t bstart, bend;
int pktlen;
if (!i82596_get_rbd_list(sc, &bstart, &bend, &pktlen))
return (1);
i82596_release_rbd_list(sc, bstart, bend);
return (0);
}
int
i82596_chk_rx_ring(struct ie_softc *sc)
{
int n, off, val;
for (n = 0; n < sc->nrxbuf; n++) {
off = IE_RBD_BUFLEN(sc->rbds, n);
val = sc->ie_bus_read16(sc, off);
if ((n == sc->rbtail) ^ ((val & IE_RBD_EOL) != 0)) {
log(LOG_ERR,
"%s: rx buffer descriptors out of sync at %d\n",
sc->sc_dev.dv_xname, n);
return (1);
}
}
for (n = 0; n < sc->nframes; n++) {
off = IE_RFRAME_LAST(sc->rframes, n);
val = sc->ie_bus_read16(sc, off);
if ((n == sc->rftail) ^ ((val & (IE_FD_EOL|IE_FD_SUSP)) != 0)) {
log(LOG_ERR,
"%s: rx frame list out of sync at %d\n",
sc->sc_dev.dv_xname, n);
return (1);
}
}
return (0);
}
static __inline__ struct mbuf *
i82596_get(struct ie_softc *sc, int head, int totlen)
{
struct mbuf *m, *m0, *newm;
int off, len, resid;
int thisrboff, thismboff;
struct ether_header eh;
(sc->memcopyin)(sc, &eh, IE_RBUF_ADDR(sc, head),
sizeof(struct ether_header));
resid = totlen;
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 == NULL)
return (0);
m0->m_pkthdr.len = totlen;
len = MHLEN;
m = m0;
while (totlen > 0) {
if (totlen >= MINCLSIZE) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0)
goto bad;
len = MCLBYTES;
}
if (m == m0) {
caddr_t newdata = (caddr_t)
ALIGN(m->m_data + sizeof(struct ether_header)) -
sizeof(struct ether_header);
len -= newdata - m->m_data;
m->m_data = newdata;
}
m->m_len = len = min(totlen, len);
totlen -= len;
if (totlen > 0) {
MGET(newm, M_DONTWAIT, MT_DATA);
if (newm == NULL)
goto bad;
len = MLEN;
m = m->m_next = newm;
}
}
m = m0;
thismboff = 0;
bcopy(&eh, mtod(m, caddr_t), sizeof(struct ether_header));
thismboff = sizeof(struct ether_header);
thisrboff = sizeof(struct ether_header);
resid -= sizeof(struct ether_header);
while (resid > 0) {
int thisrblen = IE_RBUF_SIZE - thisrboff,
thismblen = m->m_len - thismboff;
len = min(thisrblen, thismblen);
off = IE_RBUF_ADDR(sc,head) + thisrboff;
(sc->memcopyin)(sc, mtod(m, caddr_t) + thismboff, off, len);
resid -= len;
if (len == thismblen) {
m = m->m_next;
thismboff = 0;
} else
thismboff += len;
if (len == thisrblen) {
if (++head == sc->nrxbuf)
head = 0;
thisrboff = 0;
} else
thisrboff += len;
}
return (m0);
bad:
m_freem(m0);
return (NULL);
}
int
i82596_readframe(struct ie_softc *sc, int num)
{
struct ifnet *ifp = &sc->sc_arpcom.ac_if;
struct mbuf *m;
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
u_int16_t bstart, bend;
int pktlen;
if (i82596_get_rbd_list(sc, &bstart, &bend, &pktlen) == 0) {
ifp->if_ierrors++;
return (1);
}
m = i82596_get(sc, bstart, pktlen);
i82596_release_rbd_list(sc, bstart, bend);
if (m == NULL) {
sc->sc_arpcom.ac_if.if_ierrors++;
return (0);
}
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_READFRAME) {
struct ether_header *eh = mtod(m, struct ether_header *);
printf("%s: frame from ether %s type 0x%x len %d\n",
sc->sc_dev.dv_xname, ether_sprintf(eh->ether_shost),
(u_int)eh->ether_type, pktlen);
}
#endif
ml_enqueue(&ml, m);
if_input(ifp, &ml);
return (0);
}
void
i82596_xmit(struct ie_softc *sc)
{
int off, cur, prev;
cur = sc->xctail;
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_XMIT)
printf("%s: xmit buffer %d\n", sc->sc_dev.dv_xname, cur);
#endif
sc->ie_bus_write16(sc, IE_CMD_XMIT_DESC(sc->xmit_cmds, cur),
IE_XBD_ADDR(sc->xbds, cur));
sc->ie_bus_write16(sc, IE_CMD_XMIT_STATUS(sc->xmit_cmds, cur), 0);
if (sc->do_xmitnopchain) {
sc->ie_bus_write16(sc, IE_CMD_XMIT_LINK(sc->xmit_cmds, cur),
IE_CMD_NOP_ADDR(sc->nop_cmds, cur));
sc->ie_bus_write16(sc, IE_CMD_XMIT_CMD(sc->xmit_cmds, cur),
IE_CMD_XMIT | IE_CMD_INTR);
sc->ie_bus_write16(sc, IE_CMD_NOP_STATUS(sc->nop_cmds, cur), 0);
sc->ie_bus_write16(sc, IE_CMD_NOP_LINK(sc->nop_cmds, cur),
IE_CMD_NOP_ADDR(sc->nop_cmds, cur));
prev = (cur + NTXBUF - 1) % NTXBUF;
sc->ie_bus_write16(sc, IE_CMD_NOP_STATUS(sc->nop_cmds, prev), 0);
sc->ie_bus_write16(sc, IE_CMD_NOP_LINK(sc->nop_cmds, prev),
IE_CMD_XMIT_ADDR(sc->xmit_cmds, cur));
off = IE_SCB_STATUS(sc->scb);
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_READ);
if ((sc->ie_bus_read16(sc, off) & IE_CUS_ACTIVE) == 0) {
printf("i82596_xmit: CU not active\n");
i82596_start_transceiver(sc);
}
} else {
sc->ie_bus_write16(sc, IE_CMD_XMIT_LINK(sc->xmit_cmds,cur),
0xffff);
sc->ie_bus_write16(sc, IE_CMD_XMIT_CMD(sc->xmit_cmds, cur),
IE_CMD_XMIT | IE_CMD_INTR | IE_CMD_LAST);
off = IE_SCB_CMDLST(sc->scb);
sc->ie_bus_write16(sc, off, IE_CMD_XMIT_ADDR(sc->xmit_cmds, cur));
bus_space_barrier(sc->bt, sc->bh, off, 2,
BUS_SPACE_BARRIER_WRITE);
if (i82596_start_cmd(sc, IE_CUC_START, 0, 0, 1)) {
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_XMIT)
printf("%s: i82596_xmit: "
"start xmit command timed out\n",
sc->sc_dev.dv_xname);
#endif
}
}
sc->sc_arpcom.ac_if.if_timer = 5;
}
void
i82596_start(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
struct mbuf *m0, *m;
int buffer, head, xbase;
u_short len;
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_ENQ)
printf("i82596_start(%p)\n", ifp);
#endif
if (!(ifp->if_flags & IFF_RUNNING) || ifq_is_oactive(&ifp->if_snd))
return;
for (;;) {
if (sc->xmit_busy == NTXBUF) {
ifq_set_oactive(&ifp->if_snd);
break;
}
m0 = ifq_dequeue(&ifp->if_snd);
if (m0 == NULL)
break;
if ((m0->m_flags & M_PKTHDR) == 0)
panic("i82596_start: no header mbuf");
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m0, BPF_DIRECTION_OUT);
#endif
if (m0->m_pkthdr.len > IE_TBUF_SIZE)
printf("%s: tbuf overflow\n", sc->sc_dev.dv_xname);
head = sc->xchead;
sc->xchead = (head + 1) % NTXBUF;
buffer = IE_XBUF_ADDR(sc, head);
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_ENQ)
printf("%s: fill buffer %d offset %x",
sc->sc_dev.dv_xname, head, buffer);
#endif
for (m = m0; m != 0; m = m->m_next) {
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_ENQ) {
u_int8_t *e, *p = mtod(m, u_int8_t *);
static int i;
if (m == m0)
i = 0;
for (e = p + m->m_len; p < e; i++, p += 2) {
if (!(i % 8))
printf("\n%s:",
sc->sc_dev.dv_xname);
printf(" %02x%02x", p[0], p[1]);
}
}
#endif
(sc->memcopyout)(sc, mtod(m,caddr_t), buffer, m->m_len);
buffer += m->m_len;
}
len = m0->m_pkthdr.len;
if (len < ETHER_MIN_LEN - ETHER_CRC_LEN) {
(sc->memcopyout)(sc, padbuf, buffer,
ETHER_MIN_LEN - ETHER_CRC_LEN - len);
buffer += ETHER_MIN_LEN - ETHER_CRC_LEN - len;
len = ETHER_MIN_LEN - ETHER_CRC_LEN;
}
#ifdef I82596_DEBUG
if (sc->sc_debug & IED_ENQ)
printf("\n");
#endif
m_freem(m0);
xbase = sc->xbds;
sc->ie_bus_write16(sc, IE_XBD_FLAGS(xbase, head),
len | IE_TBD_EOL);
sc->ie_bus_write16(sc, IE_XBD_NEXT(xbase, head), 0xffff);
sc->ie_bus_write24(sc, IE_XBD_BUF(xbase, head),
sc->sc_maddr + IE_XBUF_ADDR(sc, head));
if (sc->xmit_busy++ == 0)
i82596_xmit(sc);
}
}
int
i82596_proberam(struct ie_softc *sc)
{
int result, off;
off = IE_SCP_BUS_USE(sc->scp);
(sc->ie_bus_write16)(sc, off, 0);
bus_space_barrier(sc->bt, sc->bh, off, 2, BUS_SPACE_BARRIER_WRITE);
off = IE_ISCP_BUSY(sc->iscp);
(sc->ie_bus_write16)(sc, off, 1);
bus_space_barrier(sc->bt, sc->bh, off, 2, BUS_SPACE_BARRIER_WRITE);
if (sc->hwreset)
(sc->hwreset)(sc, IE_CHIP_PROBE);
(sc->chan_attn) (sc);
DELAY(100);
off = IE_ISCP_BUSY(sc->iscp);
bus_space_barrier(sc->bt, sc->bh, off, 1, BUS_SPACE_BARRIER_READ);
result = (sc->ie_bus_read16)(sc, off) == 0;
ie_ack(sc, IE_ST_WHENCE);
return (result);
}
void
i82596_reset(struct ie_softc *sc, int hard)
{
int s = splnet();
#ifdef I82596_DEBUG
if (hard)
printf("%s: reset\n", sc->sc_dev.dv_xname);
#endif
sc->sc_arpcom.ac_if.if_timer = 0;
ifq_clr_oactive(&sc->sc_arpcom.ac_if.if_snd);
if (i82596_start_cmd(sc, IE_RUC_ABORT | IE_CUC_ABORT, 0, 0, 0)) {
#ifdef I82596_DEBUG
printf("%s: abort commands timed out\n", sc->sc_dev.dv_xname);
#endif
}
if (hard && sc->hwreset)
(sc->hwreset)(sc, IE_CARD_RESET);
DELAY(100);
ie_ack(sc, IE_ST_WHENCE);
if ((sc->sc_arpcom.ac_if.if_flags & IFF_UP) != 0) {
int retries=0;
while (retries++ < 2)
if (i82596_init(sc) == 1)
break;
}
splx(s);
}
void
i82596_simple_command(struct ie_softc *sc, int cmd, int cmdbuf)
{
sc->ie_bus_write16(sc, IE_CMD_COMMON_STATUS(cmdbuf), 0);
sc->ie_bus_write16(sc, IE_CMD_COMMON_CMD(cmdbuf), cmd | IE_CMD_LAST);
sc->ie_bus_write16(sc, IE_CMD_COMMON_LINK(cmdbuf), 0xffff);
sc->ie_bus_write16(sc, IE_SCB_CMDLST(sc->scb), cmdbuf);
}
void
ie_run_tdr(struct ie_softc *sc, int cmd)
{
int result, clocks;
i82596_simple_command(sc, IE_CMD_TDR, cmd);
(sc->ie_bus_write16)(sc, IE_CMD_TDR_TIME(cmd), 0);
if (i82596_start_cmd(sc, IE_CUC_START, cmd, IE_STAT_COMPL, 0) ||
!(sc->ie_bus_read16(sc, IE_CMD_COMMON_STATUS(cmd)) & IE_STAT_OK))
result = 0x10000;
else
result = sc->ie_bus_read16(sc, IE_CMD_TDR_TIME(cmd));
ie_ack(sc, IE_ST_WHENCE);
if (result & IE_TDR_SUCCESS)
return;
clocks = result & IE_TDR_TIME;
if (result & 0x10000)
printf("%s: TDR command failed\n", sc->sc_dev.dv_xname);
else if (result & IE_TDR_XCVR) {
#ifdef I82596_DEBUG
printf("%s: transceiver problem\n", sc->sc_dev.dv_xname);
#endif
} else if (result & IE_TDR_OPEN)
printf("%s: TDR detected an open %d clock%s away\n",
sc->sc_dev.dv_xname, clocks, clocks == 1? "":"s");
else if (result & IE_TDR_SHORT)
printf("%s: TDR detected a short %d clock%s away\n",
sc->sc_dev.dv_xname, clocks, clocks == 1? "":"s");
else
printf("%s: TDR returned unknown status 0x%x\n",
sc->sc_dev.dv_xname, result);
}
void
i82596_setup_bufs(struct ie_softc *sc)
{
int n, r, ptr = sc->buf_area;
int cl = 32;
ptr = (ptr + cl - 1) & ~(cl - 1);
ptr += cl;
sc->nop_cmds = ptr - 2;
ptr += NTXBUF * 32;
ptr += cl;
sc->xmit_cmds = ptr - 2;
ptr += NTXBUF * 32;
ptr += cl;
sc->xbds = ptr - 2;
ptr += NTXBUF * 32;
sc->xbufs = ptr;
ptr += NTXBUF * IE_TBUF_SIZE;
ptr = (ptr + cl - 1) & ~(cl - 1);
n = sc->buf_area_sz - (ptr - sc->buf_area);
r = 64 + ((32 + IE_RBUF_SIZE) * B_PER_F);
sc->nframes = n / r;
if (sc->nframes <= 8)
panic("ie: bogus buffer calc");
sc->nrxbuf = sc->nframes * B_PER_F;
ptr += cl;
sc->rframes = ptr - 2;
ptr += sc->nframes * 64;
ptr += cl;
sc->rbds = ptr - 2;
ptr += sc->nrxbuf * 32;
sc->rbufs = ptr;
ptr += sc->nrxbuf * IE_RBUF_SIZE;
#ifdef I82596_DEBUG
printf("%s: %d frames %d bufs\n", sc->sc_dev.dv_xname, sc->nframes,
sc->nrxbuf);
#endif
for (n = 0; n < sc->nframes; n++) {
int m = (n == sc->nframes - 1) ? 0 : n + 1;
sc->ie_bus_write16(sc, IE_RFRAME_STATUS(sc->rframes,n), 0);
sc->ie_bus_write16(sc, IE_RFRAME_BUFDESC(sc->rframes,n),
0xffff);
sc->ie_bus_write16(sc, IE_RFRAME_NEXT(sc->rframes,n),
IE_RFRAME_ADDR(sc->rframes,m));
sc->ie_bus_write16(sc, IE_RFRAME_LAST(sc->rframes,n),
((m==0)? (IE_FD_EOL|IE_FD_SUSP) : 0));
}
for (n = 0; n < sc->nrxbuf; n++) {
int m = (n == sc->nrxbuf - 1) ? 0 : n + 1;
sc->ie_bus_write16(sc, IE_RBD_STATUS(sc->rbds,n), 0);
sc->ie_bus_write16(sc, IE_RBD_NEXT(sc->rbds,n),
IE_RBD_ADDR(sc->rbds,m));
sc->ie_bus_write24(sc, IE_RBD_BUFADDR(sc->rbds, n),
sc->sc_maddr + IE_RBUF_ADDR(sc, n));
sc->ie_bus_write16(sc, IE_RBD_BUFLEN(sc->rbds,n),
IE_RBUF_SIZE | ((m==0)?IE_RBD_EOL:0));
}
for (n = 0; n < NTXBUF; n++) {
(sc->ie_bus_write16)(sc, IE_CMD_NOP_STATUS(sc->nop_cmds, n), 0);
(sc->ie_bus_write16)(sc, IE_CMD_NOP_CMD(sc->nop_cmds, n),
IE_CMD_NOP);
(sc->ie_bus_write16)(sc, IE_CMD_NOP_LINK(sc->nop_cmds, n),
IE_CMD_NOP_ADDR(sc->nop_cmds, n));
}
sc->xchead = sc->xctail = 0;
sc->xmit_busy = 0;
sc->rfhead = 0;
sc->rftail = sc->nframes - 1;
sc->rbhead = 0;
sc->rbtail = sc->nrxbuf - 1;
#ifdef I82596_DEBUG
printf("%s: reserved %d bytes\n",
sc->sc_dev.dv_xname, ptr - sc->buf_area);
#endif
}
int
ie_cfg_setup(struct ie_softc *sc, int cmd, int promiscuous, int manchester)
{
int cmdresult, status;
i82596_simple_command(sc, IE_CMD_CONFIG, cmd);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_CNT(cmd), 0x0c);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_FIFO(cmd), 8);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_SAVEBAD(cmd), 0x40);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_ADDRLEN(cmd), 0x2e);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_PRIORITY(cmd), 0);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_IFS(cmd), 0x60);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_SLOT_LOW(cmd), 0);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_SLOT_HIGH(cmd), 0xf2);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_PROMISC(cmd),
!!promiscuous | manchester << 2);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_CRSCDT(cmd), 0);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_MINLEN(cmd), 64);
bus_space_write_1(sc->bt, sc->bh, IE_CMD_CFG_JUNK(cmd), 0xff);
bus_space_barrier(sc->bt, sc->bh, cmd, IE_CMD_CFG_SZ,
BUS_SPACE_BARRIER_WRITE);
cmdresult = i82596_start_cmd(sc, IE_CUC_START, cmd, IE_STAT_COMPL, 0);
status = sc->ie_bus_read16(sc, IE_CMD_COMMON_STATUS(cmd));
if (cmdresult != 0) {
printf("%s: configure command timed out; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
printf("%s: configure command failed; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
int
ie_ia_setup(struct ie_softc *sc, int cmdbuf)
{
int cmdresult, status;
i82596_simple_command(sc, IE_CMD_IASETUP, cmdbuf);
(sc->memcopyout)(sc, sc->sc_arpcom.ac_enaddr,
IE_CMD_IAS_EADDR(cmdbuf), ETHER_ADDR_LEN);
cmdresult = i82596_start_cmd(sc, IE_CUC_START, cmdbuf, IE_STAT_COMPL, 0);
status = sc->ie_bus_read16(sc, IE_CMD_COMMON_STATUS(cmdbuf));
if (cmdresult != 0) {
printf("%s: individual address command timed out; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
printf("%s: individual address command failed; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
int
ie_mc_setup(struct ie_softc *sc, int cmdbuf)
{
int cmdresult, status;
if (sc->mcast_count == 0)
return (1);
i82596_simple_command(sc, IE_CMD_MCAST, cmdbuf);
(sc->memcopyout)(sc, (caddr_t)sc->mcast_addrs,
IE_CMD_MCAST_MADDR(cmdbuf),
sc->mcast_count * ETHER_ADDR_LEN);
sc->ie_bus_write16(sc, IE_CMD_MCAST_BYTES(cmdbuf),
sc->mcast_count * ETHER_ADDR_LEN);
cmdresult = i82596_start_cmd(sc, IE_CUC_START, cmdbuf, IE_STAT_COMPL, 0);
status = sc->ie_bus_read16(sc, IE_CMD_COMMON_STATUS(cmdbuf));
if (cmdresult != 0) {
printf("%s: multicast setup command timed out; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
printf("%s: multicast setup command failed; status %b\n",
sc->sc_dev.dv_xname, status, IE_STAT_BITS);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
int
i82596_init(struct ie_softc *sc)
{
struct ifnet *ifp = &sc->sc_arpcom.ac_if;
int cmd;
sc->async_cmd_inprogress = 0;
cmd = sc->buf_area;
if (ie_cfg_setup(sc, cmd, sc->promisc, 0) == 0)
return (0);
if (ie_ia_setup(sc, cmd) == 0)
return (0);
ie_run_tdr(sc, cmd);
if (ie_mc_setup(sc, cmd) == 0)
return (0);
ie_ack(sc, IE_ST_WHENCE);
i82596_setup_bufs(sc);
if (sc->hwinit)
(sc->hwinit)(sc);
ifp->if_flags |= IFF_RUNNING;
ifq_clr_oactive(&ifp->if_snd);
if (NTXBUF < 2)
sc->do_xmitnopchain = 0;
i82596_start_transceiver(sc);
return (1);
}
void
i82596_start_transceiver(struct ie_softc *sc)
{
sc->ie_bus_write16(sc, IE_RFRAME_BUFDESC(sc->rframes,sc->rfhead),
IE_RBD_ADDR(sc->rbds, sc->rbhead));
sc->ie_bus_write16(sc, IE_SCB_RCVLST(sc->scb),
IE_RFRAME_ADDR(sc->rframes,sc->rfhead));
if (sc->do_xmitnopchain) {
if (i82596_start_cmd(sc, IE_CUC_SUSPEND|IE_RUC_SUSPEND, 0, 0, 0))
printf("%s: CU/RU stop command timed out\n",
sc->sc_dev.dv_xname);
sc->ie_bus_write16(sc, IE_SCB_CMDLST(sc->scb),
IE_CMD_NOP_ADDR(
sc->nop_cmds,
(sc->xctail + NTXBUF - 1) % NTXBUF));
if (i82596_start_cmd(sc, IE_CUC_START|IE_RUC_START, 0, 0, 0))
printf("%s: CU/RU command timed out\n",
sc->sc_dev.dv_xname);
} else {
if (i82596_start_cmd(sc, IE_RUC_START, 0, 0, 0))
printf("%s: RU command timed out\n",
sc->sc_dev.dv_xname);
}
}
void
i82596_stop(struct ie_softc *sc)
{
if (i82596_start_cmd(sc, IE_RUC_SUSPEND | IE_CUC_SUSPEND, 0, 0, 0))
printf("%s: i82596_stop: disable commands timed out\n",
sc->sc_dev.dv_xname);
}
int
i82596_ioctl(register struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct ie_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
int s, error = 0;
s = splnet();
switch(cmd) {
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP;
i82596_init(sc);
break;
case SIOCSIFFLAGS:
sc->promisc = ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI);
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING) != 0) {
i82596_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
} else if ((ifp->if_flags & IFF_UP) != 0 &&
(ifp->if_flags & IFF_RUNNING) == 0) {
i82596_init(sc);
} else {
i82596_stop(sc);
i82596_init(sc);
}
#ifdef I82596_DEBUG
if (ifp->if_flags & IFF_DEBUG)
sc->sc_debug = IED_ALL;
else
sc->sc_debug = 0;
#endif
break;
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd);
break;
default:
error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data);
}
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
ie_mc_reset(sc);
error = 0;
}
splx(s);
return (error);
}
void
ie_mc_reset(struct ie_softc *sc)
{
struct arpcom *ac = &sc->sc_arpcom;
struct ether_multi *enm;
struct ether_multistep step;
int size;
if (ac->ac_multicnt >= IE_MAXMCAST || ac->ac_multirangecnt > 0) {
ac->ac_if.if_flags |= IFF_ALLMULTI;
i82596_ioctl(&ac->ac_if, SIOCSIFFLAGS, NULL);
return;
}
size = 0;
sc->mcast_count = 0;
ETHER_FIRST_MULTI(step, &sc->sc_arpcom, enm);
while (enm) {
size += ETHER_ADDR_LEN;
sc->mcast_count++;
ETHER_NEXT_MULTI(step, enm);
}
if (size > sc->mcast_addrs_size) {
if (sc->mcast_addrs_size)
free(sc->mcast_addrs, M_IFMADDR, 0);
sc->mcast_addrs = (char *)
malloc(size, M_IFMADDR, M_WAITOK);
sc->mcast_addrs_size = size;
}
sc->mcast_count = 0;
ETHER_FIRST_MULTI(step, &sc->sc_arpcom, enm);
while (enm) {
bcopy(enm->enm_addrlo,
&sc->mcast_addrs[sc->mcast_count * ETHER_ADDR_LEN],
ETHER_ADDR_LEN);
sc->mcast_count++;
ETHER_NEXT_MULTI(step, enm);
}
sc->want_mcsetup = 1;
}
int
i82596_mediachange(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
if (sc->sc_mediachange)
return ((*sc->sc_mediachange)(sc));
return (EINVAL);
}
void
i82596_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct ie_softc *sc = ifp->if_softc;
if (sc->sc_mediastatus)
(*sc->sc_mediastatus)(sc, ifmr);
}
#ifdef I82596_DEBUG
void
print_rbd(struct ie_softc *sc, int n)
{
printf("RBD at %08x:\n status %b, next %04x, buffer %lx\n"
"length/EOL %04x\n", IE_RBD_ADDR(sc->rbds,n),
sc->ie_bus_read16(sc, IE_RBD_STATUS(sc->rbds,n)), IE_STAT_BITS,
sc->ie_bus_read16(sc, IE_RBD_NEXT(sc->rbds,n)),
(u_long)0,
sc->ie_bus_read16(sc, IE_RBD_BUFLEN(sc->rbds,n)));
}
#endif