#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: i82586.c,v 1.91 2024/02/09 22:08:34 andvar Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#include <dev/ic/i82586reg.h>
#include <dev/ic/i82586var.h>
void i82586_reset(struct ie_softc *, int);
void i82586_watchdog(struct ifnet *);
int i82586_init(struct ifnet *);
int i82586_ioctl(struct ifnet *, u_long, void *);
void i82586_start(struct ifnet *);
void i82586_stop(struct ifnet *, int);
int i82586_rint(struct ie_softc *, int);
int i82586_tint(struct ie_softc *, int);
int i82586_mediachange(struct ifnet *);
void i82586_mediastatus(struct ifnet *, struct ifmediareq *);
static int ie_readframe(struct ie_softc *, int);
static struct mbuf *ieget(struct ie_softc *, int, int);
static int i82586_get_rbd_list(struct ie_softc *,
uint16_t *, uint16_t *, int *);
static void i82586_release_rbd_list(struct ie_softc *,
uint16_t, uint16_t);
static int i82586_drop_frames(struct ie_softc *);
static int i82586_chk_rx_ring(struct ie_softc *);
static inline void ie_ack(struct ie_softc *, u_int);
static inline void iexmit(struct ie_softc *);
static void i82586_start_transceiver(struct ie_softc *);
static void i82586_count_errors(struct ie_softc *);
static void i82586_rx_errors(struct ie_softc *, int, int);
static void i82586_setup_bufs(struct ie_softc *);
static void setup_simple_command(struct ie_softc *, int, int);
static int ie_cfg_setup(struct ie_softc *, int, int, int);
static int ie_ia_setup(struct ie_softc *, int);
static void ie_run_tdr(struct ie_softc *, int);
static int ie_mc_setup(struct ie_softc *, int);
static void ie_mc_reset(struct ie_softc *);
static int i82586_start_cmd(struct ie_softc *, int, int, int, int);
static int i82586_cmd_wait(struct ie_softc *);
#if I82586_DEBUG
void print_rbd(struct ie_softc *, int);
#endif
static char* padbuf = NULL;
void
i82586_attach(struct ie_softc *sc, const char *name, uint8_t *etheraddr,
int *media, int nmedia, int defmedia)
{
int i;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_start = i82586_start;
ifp->if_ioctl = i82586_ioctl;
ifp->if_init = i82586_init;
ifp->if_stop = i82586_stop;
ifp->if_watchdog = i82586_watchdog;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
IFQ_SET_READY(&ifp->if_snd);
sc->sc_ethercom.ec_ifmedia = &sc->sc_media;
ifmedia_init(&sc->sc_media, 0, i82586_mediachange, i82586_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_ZERO | M_WAITOK);
}
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, etheraddr);
aprint_normal(" address %s, type %s\n", ether_sprintf(etheraddr),name);
}
void
i82586_watchdog(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
log(LOG_ERR, "%s: device timeout\n", device_xname(sc->sc_dev));
if_statinc(ifp, if_oerrors);
i82586_reset(sc, 1);
}
static int
i82586_cmd_wait(struct ie_softc *sc)
{
int i, off;
uint16_t cmd;
for (i = 0; i < 900000; i++) {
off = IE_SCB_CMD(sc->scb);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
if ((cmd = sc->ie_bus_read16(sc, off)) == 0)
return (0);
delay(1);
}
off = IE_SCB_STATUS(sc->scb);
printf("i82586_cmd_wait: timo(%ssync): scb status: 0x%x, cmd: 0x%x\n",
sc->async_cmd_inprogress?"a":"",
sc->ie_bus_read16(sc, off), cmd);
return (1);
}
static int
i82586_start_cmd(struct ie_softc *sc, int cmd, int iecmdbuf, int mask,
int async)
{
int i;
int off;
if (sc->async_cmd_inprogress != 0) {
if (i82586_cmd_wait(sc) != 0)
return (1);
sc->async_cmd_inprogress = 0;
}
off = IE_SCB_CMD(sc->scb);
sc->ie_bus_write16(sc, off, cmd);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_WRITE);
(sc->chan_attn)(sc, CARD_RESET);
if (async != 0) {
sc->async_cmd_inprogress = 1;
return (0);
}
if (IE_ACTION_COMMAND(cmd) && iecmdbuf) {
int status;
for (i = 0; i < 369000; i++) {
off = IE_CMD_COMMON_STATUS(iecmdbuf);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off);
if (status & mask)
return (0);
delay(1);
}
} else {
return (i82586_cmd_wait(sc));
}
return (1);
}
static inline void
ie_ack(struct ie_softc *sc, u_int mask)
{
u_int status;
IE_BUS_BARRIER(sc, 0, 0, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, IE_SCB_STATUS(sc->scb));
i82586_start_cmd(sc, status & mask, 0, 0, 0);
if (sc->intrhook)
sc->intrhook(sc, INTR_ACK);
}
static inline void
i82586_count_errors(struct ie_softc *sc)
{
int scb = sc->scb;
if_statadd(&sc->sc_ethercom.ec_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 void
i82586_rx_errors(struct ie_softc *sc, int fn, int status)
{
char bits[128];
snprintb(bits, sizeof(bits), IE_FD_STATUSBITS, status);
log(LOG_ERR, "%s: rx error (frame# %d): %s\n",
device_xname(sc->sc_dev), fn, bits);
}
int
i82586_intr(void *v)
{
struct ie_softc *sc = v;
u_int status;
int off;
if (sc->intrhook)
(sc->intrhook)(sc, INTR_ENTER);
off = IE_SCB_STATUS(sc->scb);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off) & IE_ST_WHENCE;
if ((status & IE_ST_WHENCE) == 0) {
if (sc->intrhook)
(sc->intrhook)(sc, INTR_EXIT);
return (0);
}
loop:
#if 0
ie_ack(sc, status & IE_ST_WHENCE);
#endif
i82586_start_cmd(sc, status & IE_ST_WHENCE, 0, 0, 1);
if (status & (IE_ST_FR | IE_ST_RNR))
if (i82586_rint(sc, status) != 0)
goto reset;
if (status & IE_ST_CX)
if (i82586_tint(sc, status) != 0)
goto reset;
#if I82586_DEBUG
if ((status & IE_ST_CNA) && (sc->sc_debug & IED_CNA))
printf("%s: cna; status=0x%x\n", device_xname(sc->sc_dev),
status);
#endif
if (sc->intrhook)
(sc->intrhook)(sc, INTR_LOOP);
if (i82586_cmd_wait(sc) != 0)
goto reset;
IE_BUS_BARRIER(sc, 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, INTR_EXIT);
return (1);
reset:
i82586_cmd_wait(sc);
i82586_reset(sc, 1);
goto out;
}
int
i82586_rint(struct ie_softc *sc, int scbstatus)
{
static int timesthru = 1024;
int i, status, off;
#if I82586_DEBUG
if (sc->sc_debug & IED_RINT)
printf("%s: rint: status 0x%x\n",
device_xname(sc->sc_dev), scbstatus);
#endif
for (;;) {
int drop = 0;
i = sc->rfhead;
off = IE_RFRAME_STATUS(sc->rframes, i);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
status = sc->ie_bus_read16(sc, off);
#if I82586_DEBUG
if (sc->sc_debug & IED_RINT)
printf("%s: rint: frame(%d) status 0x%x\n",
device_xname(sc->sc_dev), i, status);
#endif
if ((status & IE_FD_COMPLETE) == 0) {
if ((status & IE_FD_OK) != 0) {
printf("%s: rint: weird: ",
device_xname(sc->sc_dev));
i82586_rx_errors(sc, i, status);
break;
}
if (--timesthru == 0) {
i82586_count_errors(sc);
timesthru = 1024;
}
break;
} else if ((status & IE_FD_OK) == 0) {
i82586_rx_errors(sc, i, status);
drop = 1;
}
#if I82586_DEBUG
if ((status & IE_FD_BUSY) != 0)
printf("%s: rint: frame(%d) busy; status=0x%x\n",
device_xname(sc->sc_dev), i, status);
#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) {
i82586_drop_frames(sc);
if ((status & IE_FD_RNR) != 0)
sc->rnr_expect = 1;
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
} else if (ie_readframe(sc, i) != 0)
return (1);
}
if ((scbstatus & IE_ST_RNR) != 0) {
if ((scbstatus & IE_RUS_SUSPEND) != 0) {
printf("RINT: SUSPENDED; scbstatus=0x%x\n",
scbstatus);
if (i82586_start_cmd(sc, IE_RUC_RESUME, 0, 0, 0) == 0)
return (0);
aprint_error_dev(sc->sc_dev,
"RU RESUME command timed out\n");
return (1);
}
if (sc->rnr_expect != 0) {
i82586_start_transceiver(sc);
sc->rnr_expect = 0;
return (0);
} else if ((scbstatus & IE_RUS_NOSPACE) != 0) {
if (i82586_chk_rx_ring(sc) != 0)
return (1);
i82586_start_transceiver(sc);
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
return (0);
} else
printf("%s: receiver not ready; scbstatus=0x%x\n",
device_xname(sc->sc_dev), scbstatus);
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
return (1);
}
return (0);
}
int
i82586_tint(struct ie_softc *sc, int scbstatus)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
int status;
ifp->if_timer = 0;
#if I82586_DEBUG
if (sc->xmit_busy <= 0) {
printf("i82586_tint: WEIRD: xmit_busy=%d, xctail=%d, xchead=%d\n",
sc->xmit_busy, sc->xctail, sc->xchead);
return (0);
}
#endif
status = sc->ie_bus_read16(sc, IE_CMD_XMIT_STATUS(sc->xmit_cmds,
sc->xctail));
#if I82586_DEBUG
if (sc->sc_debug & IED_TINT)
printf("%s: tint: SCB status 0x%x; xmit status 0x%x\n",
device_xname(sc->sc_dev), scbstatus, status);
#endif
if ((status & IE_STAT_COMPL) == 0 || (status & IE_STAT_BUSY)) {
printf("i82586_tint: command still busy; status=0x%x; tail=%d\n",
status, sc->xctail);
printf("iestatus = 0x%x\n", scbstatus);
}
if (status & IE_STAT_OK) {
if_statadd2(ifp, if_opackets, 1,
if_collisions, status & IE_XS_MAXCOLL);
} else {
if_statinc(ifp, if_oerrors);
if (status & IE_STAT_ABORT)
aprint_error_dev(sc->sc_dev, "send aborted\n");
else if (status & IE_XS_NOCARRIER)
aprint_error_dev(sc->sc_dev, "no carrier\n");
else if (status & IE_XS_LOSTCTS)
aprint_error_dev(sc->sc_dev, "lost CTS\n");
else if (status & IE_XS_UNDERRUN)
aprint_error_dev(sc->sc_dev, "DMA underrun\n");
else if (status & IE_XS_EXCMAX) {
aprint_error_dev(sc->sc_dev, "too many collisions\n");
if_statadd(&sc->sc_ethercom.ec_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)
iexmit(sc);
if_schedule_deferred_start(ifp);
return (0);
}
static int
i82586_get_rbd_list(struct ie_softc *sc, uint16_t *start, uint16_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);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
rbdstatus = sc->ie_bus_read16(sc, off);
if ((rbdstatus & IE_RBD_USED) == 0) {
#if I82586_DEBUG
print_rbd(sc, rbindex);
#endif
log(LOG_ERR,
"%s: receive descriptors out of sync at %d\n",
device_xname(sc->sc_dev), 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);
}
static void
i82586_release_rbd_list(struct ie_softc *sc, uint16_t start, uint16_t end)
{
int off, rbbase = sc->rbds;
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;
}
static int
i82586_drop_frames(struct ie_softc *sc)
{
uint16_t bstart, bend;
int pktlen;
if (i82586_get_rbd_list(sc, &bstart, &bend, &pktlen) == 0)
return (1);
i82586_release_rbd_list(sc, bstart, bend);
return (0);
}
static int
i82586_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",
device_xname(sc->sc_dev), 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",
device_xname(sc->sc_dev), n);
return (1);
}
}
return (0);
}
static inline struct mbuf *
ieget(struct ie_softc *sc, int head, int totlen)
{
struct mbuf *m, *m0, *newm;
int 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 == 0)
return (0);
m_set_rcvif(m0, &sc->sc_ethercom.ec_if);
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) {
char *newdata = (char *)
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 = uimin(totlen, len);
totlen -= len;
if (totlen > 0) {
MGET(newm, M_DONTWAIT, MT_DATA);
if (newm == 0)
goto bad;
len = MLEN;
m = m->m_next = newm;
}
}
m = m0;
thismboff = 0;
memcpy(mtod(m, void *), &eh, 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 = uimin(thisrblen, thismblen);
(sc->memcopyin)(sc, mtod(m, char *) + thismboff,
IE_RBUF_ADDR(sc, head) + thisrboff,
(u_int)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 (0);
}
static int
ie_readframe(
struct ie_softc *sc,
int num)
{
struct mbuf *m;
uint16_t bstart, bend;
int pktlen;
if (i82586_get_rbd_list(sc, &bstart, &bend, &pktlen) == 0) {
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
return (1);
}
m = ieget(sc, bstart, pktlen);
i82586_release_rbd_list(sc, bstart, bend);
if (m == 0) {
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
return (0);
}
#if I82586_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",
device_xname(sc->sc_dev),
ether_sprintf(eh->ether_shost),
(u_int)ntohs(eh->ether_type),
pktlen);
}
#endif
if_percpuq_enqueue((&sc->sc_ethercom.ec_if)->if_percpuq, m);
return (0);
}
static inline void
iexmit(struct ie_softc *sc)
{
int off;
int cur, prev;
cur = sc->xctail;
#if I82586_DEBUG
if (sc->sc_debug & IED_XMIT)
printf("%s: xmit buffer %d\n", device_xname(sc->sc_dev), 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);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
if ((sc->ie_bus_read16(sc, off) & IE_CUS_ACTIVE) == 0) {
printf("iexmit: CU not active\n");
i82586_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));
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
if (i82586_start_cmd(sc, IE_CUC_START, 0, 0, 1))
aprint_error_dev(sc->sc_dev,
"iexmit: start xmit command timed out\n");
}
sc->sc_ethercom.ec_if.if_timer = 5;
}
void
i82586_start(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
struct mbuf *m0, *m;
int buffer, head, xbase;
u_short len;
int s;
if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING)
return;
while (sc->xmit_busy < NTXBUF) {
head = sc->xchead;
xbase = sc->xbds;
IFQ_DEQUEUE(&ifp->if_snd, m0);
if (m0 == 0)
break;
if ((m0->m_flags & M_PKTHDR) == 0)
panic("i82586_start: no header mbuf");
bpf_mtap(ifp, m0, BPF_D_OUT);
#if I82586_DEBUG
if (sc->sc_debug & IED_ENQ)
printf("%s: fill buffer %d\n", device_xname(sc->sc_dev),
sc->xchead);
#endif
if (m0->m_pkthdr.len > IE_TBUF_SIZE)
printf("%s: tbuf overflow\n", device_xname(sc->sc_dev));
buffer = IE_XBUF_ADDR(sc, head);
for (m = m0; m != 0; m = m->m_next) {
(sc->memcopyout)(sc, mtod(m,void *), 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;
}
m_freem(m0);
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),
IE_XBUF_ADDR(sc, head));
if (++head == NTXBUF)
head = 0;
sc->xchead = head;
s = splnet();
if (sc->xmit_busy == 0)
iexmit(sc);
sc->xmit_busy++;
splx(s);
}
}
int
i82586_proberam(struct ie_softc *sc)
{
int result, off;
off = IE_SCP_BUS_USE(sc->scp);
sc->ie_bus_write16(sc, off, IE_SYSBUS_16BIT);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_WRITE);
off = IE_ISCP_BUSY(sc->iscp);
sc->ie_bus_write16(sc, off, 1);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_WRITE);
if (sc->hwreset)
(sc->hwreset)(sc, CHIP_PROBE);
(sc->chan_attn) (sc, CHIP_PROBE);
delay(100);
off = IE_ISCP_BUSY(sc->iscp);
IE_BUS_BARRIER(sc, off, 2, BUS_SPACE_BARRIER_READ);
result = sc->ie_bus_read16(sc, off) == 0;
ie_ack(sc, IE_ST_WHENCE);
return (result);
}
void
i82586_reset(struct ie_softc *sc, int hard)
{
int s = splnet();
if (hard)
printf("%s: reset\n", device_xname(sc->sc_dev));
sc->sc_ethercom.ec_if.if_timer = 0;
if (i82586_start_cmd(sc, IE_RUC_ABORT | IE_CUC_ABORT, 0, 0, 0))
aprint_error_dev(sc->sc_dev, "abort commands timed out\n");
if (hard && sc->hwreset)
(sc->hwreset)(sc, CARD_RESET);
delay(100);
ie_ack(sc, IE_ST_WHENCE);
if ((sc->sc_ethercom.ec_if.if_flags & IFF_UP) != 0) {
int retries=0;
while (retries++ < 2)
if (i82586_init(&sc->sc_ethercom.ec_if) == 0)
break;
}
splx(s);
}
static void
setup_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);
}
static void
ie_run_tdr(struct ie_softc *sc, int cmd)
{
int result;
setup_simple_command(sc, IE_CMD_TDR, cmd);
sc->ie_bus_write16(sc, IE_CMD_TDR_TIME(cmd), 0);
if (i82586_start_cmd(sc, IE_CUC_START, cmd, IE_STAT_COMPL, 0) ||
(sc->ie_bus_read16(sc, IE_CMD_COMMON_STATUS(cmd)) & IE_STAT_OK) == 0)
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;
if (result & 0x10000)
aprint_error_dev(sc->sc_dev, "TDR command failed\n");
else if (result & IE_TDR_XCVR)
aprint_error_dev(sc->sc_dev, "transceiver problem\n");
else if (result & IE_TDR_OPEN)
aprint_error_dev(sc->sc_dev, "TDR detected incorrect "
"termination %d clocks away\n", result & IE_TDR_TIME);
else if (result & IE_TDR_SHORT)
aprint_error_dev(sc->sc_dev, "TDR detected a short circuit "
"%d clocks away\n", result & IE_TDR_TIME);
else
aprint_error_dev(sc->sc_dev,
"TDR returned unknown status 0x%x\n", result);
}
static void
i82586_setup_bufs(struct ie_softc *sc)
{
int ptr = sc->buf_area;
int n, r;
ptr = (ptr + 3) & ~3;
sc->nop_cmds = ptr;
ptr += NTXBUF * IE_CMD_NOP_SZ;
sc->xmit_cmds = ptr;
ptr += NTXBUF * IE_CMD_XMIT_SZ;
sc->xbds = ptr;
ptr += NTXBUF * IE_XBD_SZ;
sc->xbufs = ptr;
ptr += NTXBUF * IE_TBUF_SIZE;
ptr = (ptr + 3) & ~3;
n = sc->buf_area_sz - (ptr - sc->buf_area);
r = IE_RFRAME_SZ + ((IE_RBD_SZ + IE_RBUF_SIZE) * B_PER_F);
sc->nframes = n / r;
if (sc->nframes <= 0)
panic("ie: bogus buffer calc");
sc->nrxbuf = sc->nframes * B_PER_F;
sc->rframes = ptr;
ptr += sc->nframes * IE_RFRAME_SZ;
sc->rbds = ptr;
ptr += sc->nrxbuf * IE_RBD_SZ;
sc->rbufs = ptr;
ptr += sc->nrxbuf * IE_RBUF_SIZE;
#if I82586_DEBUG
printf("%s: %d frames %d bufs\n", device_xname(sc->sc_dev),
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),
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;
#if I82586_DEBUG
printf("%s: reserved %d bytes\n",
device_xname(sc->sc_dev), ptr - sc->buf_area);
#endif
}
static int
ie_cfg_setup(struct ie_softc *sc, int cmd, int promiscuous, int manchester)
{
int cmdresult, status;
uint8_t buf[IE_CMD_CFG_SZ];
*IE_CMD_CFG_CNT(buf) = 0x0c;
*IE_CMD_CFG_FIFO(buf) = 8;
*IE_CMD_CFG_SAVEBAD(buf) = 0x40;
*IE_CMD_CFG_ADDRLEN(buf) = 0x2e;
*IE_CMD_CFG_PRIORITY(buf) = 0;
*IE_CMD_CFG_IFS(buf) = 0x60;
*IE_CMD_CFG_SLOT_LOW(buf) = 0;
*IE_CMD_CFG_SLOT_HIGH(buf) = 0xf2;
*IE_CMD_CFG_PROMISC(buf) = (!!promiscuous) | manchester << 2;
*IE_CMD_CFG_CRSCDT(buf) = 0;
*IE_CMD_CFG_MINLEN(buf) = 64;
*IE_CMD_CFG_JUNK(buf) = 0xff;
sc->memcopyout(sc, buf, cmd, IE_CMD_CFG_SZ);
setup_simple_command(sc, IE_CMD_CONFIG, cmd);
IE_BUS_BARRIER(sc, cmd, IE_CMD_CFG_SZ, BUS_SPACE_BARRIER_WRITE);
cmdresult = i82586_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) {
aprint_error_dev(sc->sc_dev,
"configure command timed out; status %x\n", status);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
aprint_error_dev(sc->sc_dev,
"configure command failed; status %x\n", status);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
static int
ie_ia_setup(struct ie_softc *sc, int cmdbuf)
{
int cmdresult, status;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
setup_simple_command(sc, IE_CMD_IASETUP, cmdbuf);
(sc->memcopyout)(sc, CLLADDR(ifp->if_sadl),
IE_CMD_IAS_EADDR(cmdbuf), ETHER_ADDR_LEN);
cmdresult = i82586_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) {
aprint_error_dev(sc->sc_dev,
"individual address command timed out; status %x\n",
status);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
aprint_error_dev(sc->sc_dev,
"individual address command failed; status %x\n", status);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
static int
ie_mc_setup(struct ie_softc *sc, int cmdbuf)
{
int cmdresult, status;
if (sc->mcast_count == 0)
return (1);
setup_simple_command(sc, IE_CMD_MCAST, cmdbuf);
(sc->memcopyout)(sc, (void *)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 = i82586_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) {
aprint_error_dev(sc->sc_dev,
"multicast setup command timed out; status %x\n", status);
return (0);
}
if ((status & IE_STAT_OK) == 0) {
aprint_error_dev(sc->sc_dev,
"multicast setup command failed; status %x\n", status);
return (0);
}
ie_ack(sc, IE_ST_WHENCE);
return (1);
}
int
i82586_init(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
int cmd;
sc->async_cmd_inprogress = 0;
cmd = sc->buf_area;
if (ie_cfg_setup(sc, cmd, sc->promisc, 0) == 0)
return EIO;
if (ie_ia_setup(sc, cmd) == 0)
return EIO;
ie_run_tdr(sc, cmd);
if (ie_mc_setup(sc, cmd) == 0)
return EIO;
ie_ack(sc, IE_ST_WHENCE);
i82586_setup_bufs(sc);
if (sc->hwinit)
(sc->hwinit)(sc);
ifp->if_flags |= IFF_RUNNING;
if (NTXBUF < 2)
sc->do_xmitnopchain = 0;
i82586_start_transceiver(sc);
return (0);
}
static void
i82586_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 (i82586_start_cmd(sc, IE_CUC_SUSPEND | IE_RUC_SUSPEND,
0, 0, 0))
aprint_error_dev(sc->sc_dev,
"CU/RU stop command timed out\n");
sc->ie_bus_write16(sc, IE_SCB_CMDLST(sc->scb),
IE_CMD_NOP_ADDR(
sc->nop_cmds,
(sc->xctail + NTXBUF - 1) % NTXBUF));
if (i82586_start_cmd(sc, IE_CUC_START | IE_RUC_START, 0, 0, 0))
aprint_error_dev(sc->sc_dev,
"CU/RU command timed out\n");
} else {
if (i82586_start_cmd(sc, IE_RUC_START, 0, 0, 0))
aprint_error_dev(sc->sc_dev, "RU command timed out\n");
}
}
void
i82586_stop(struct ifnet *ifp, int disable)
{
struct ie_softc *sc = ifp->if_softc;
if (i82586_start_cmd(sc, IE_RUC_SUSPEND | IE_CUC_SUSPEND, 0, 0, 0))
aprint_error_dev(sc->sc_dev,
"iestop: disable commands timed out\n");
}
int
i82586_ioctl(struct ifnet *ifp, unsigned long cmd, void *data)
{
struct ie_softc *sc = ifp->if_softc;
int s, error = 0;
s = splnet();
switch (cmd) {
default:
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
ie_mc_reset(sc);
error = 0;
}
break;
}
#if I82586_DEBUG
if (cmd == SIOCSIFFLAGS)
sc->sc_debug = (ifp->if_flags & IFF_DEBUG) ? IED_ALL : 0;
#endif
splx(s);
return (error);
}
static void
ie_mc_reset(struct ie_softc *sc)
{
struct ethercom *ec = &sc->sc_ethercom;
struct ether_multi *enm;
struct ether_multistep step;
int size;
again:
size = 0;
sc->mcast_count = 0;
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm) {
size += 6;
if (sc->mcast_count >= IE_MAXMCAST ||
memcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) {
ec->ec_if.if_flags |= IFF_ALLMULTI;
i82586_ioctl(&ec->ec_if, SIOCSIFFLAGS, NULL);
ETHER_UNLOCK(ec);
return;
}
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
if (size > sc->mcast_addrs_size) {
if (sc->mcast_addrs_size)
free(sc->mcast_addrs, M_IFMADDR);
sc->mcast_addrs = (char *)
malloc(size, M_IFMADDR, M_WAITOK);
sc->mcast_addrs_size = size;
}
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm) {
if (sc->mcast_count >= IE_MAXMCAST) {
ETHER_UNLOCK(ec);
goto again;
}
memcpy(&sc->mcast_addrs[sc->mcast_count], enm->enm_addrlo, 6);
sc->mcast_count++;
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
sc->want_mcsetup = 1;
}
int
i82586_mediachange(struct ifnet *ifp)
{
struct ie_softc *sc = ifp->if_softc;
if (sc->sc_mediachange)
return ((*sc->sc_mediachange)(sc));
return (0);
}
void
i82586_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct ie_softc *sc = ifp->if_softc;
if (sc->sc_mediastatus)
(*sc->sc_mediastatus)(sc, ifmr);
}
#if I82586_DEBUG
void
print_rbd(struct ie_softc *sc, int n)
{
printf("RBD at %08x:\n status %04x, 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)),
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