#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_sq.c,v 1.61 2026/06/11 00:47:24 rumble Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <uvm/uvm_extern.h>
#include <machine/endian.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#include <sys/bus.h>
#include <machine/intr.h>
#include <machine/sysconf.h>
#include <dev/ic/seeq8003reg.h>
#include <sgimips/hpc/sqvar.h>
#include <sgimips/hpc/hpcvar.h>
#include <sgimips/hpc/hpcreg.h>
#include <dev/arcbios/arcbios.h>
#include <dev/arcbios/arcbiosvar.h>
#define static
#if defined(SQ_DEBUG)
int sq_debug = 0;
#define SQ_DPRINTF(x) if (sq_debug) printf x
#else
#define SQ_DPRINTF(x)
#endif
static int sq_match(device_t, cfdata_t, void *);
static void sq_attach(device_t, device_t, void *);
static int sq_init(struct ifnet *);
static void sq_start(struct ifnet *);
static void sq_stop(struct ifnet *, int);
static void sq_watchdog(struct ifnet *);
static int sq_ioctl(struct ifnet *, u_long, void *);
static void sq_set_filter(struct sq_softc *);
static int sq_intr(void *);
static int sq_rxintr(struct sq_softc *);
static int sq_txintr(struct sq_softc *);
static void sq_txring_hpc1(struct sq_softc *);
static void sq_txring_hpc3(struct sq_softc *);
static void sq_reset(struct sq_softc *);
static int sq_add_rxbuf(struct sq_softc *, int);
static void sq_dump_buffer(paddr_t, psize_t);
static void sq_trace_dump(struct sq_softc *);
CFATTACH_DECL_NEW(sq, sizeof(struct sq_softc),
sq_match, sq_attach, NULL, NULL);
#define ETHER_PAD_LEN (ETHER_MIN_LEN - ETHER_CRC_LEN)
#define sq_seeq_read(sc, off) \
bus_space_read_1(sc->sc_regt, sc->sc_regh, (off << 2) + 3)
#define sq_seeq_write(sc, off, val) \
bus_space_write_1(sc->sc_regt, sc->sc_regh, (off << 2) + 3, val)
#define sq_hpc_read(sc, off) \
bus_space_read_4(sc->sc_hpct, sc->sc_hpch, off)
#define sq_hpc_write(sc, off, val) \
bus_space_write_4(sc->sc_hpct, sc->sc_hpch, off, val)
#define SQ_ONB_HPC_EEPROM_ENADDR 122
#define SQ_EXT_HPC_EEPROM_ENADDR 250
#define SGI_OUI_0 0x08
#define SGI_OUI_1 0x00
#define SGI_OUI_2 0x69
static int
sq_match(device_t parent, cfdata_t cf, void *aux)
{
struct hpc_attach_args *ha = aux;
if (strcmp(ha->ha_name, cf->cf_name) == 0) {
vaddr_t reset, txstat;
reset = MIPS_PHYS_TO_KSEG1(ha->ha_sh +
ha->ha_dmaoff + ha->hpc_regs->enetr_reset);
txstat = MIPS_PHYS_TO_KSEG1(ha->ha_sh +
ha->ha_devoff + (SEEQ_TXSTAT << 2));
if (platform.badaddr((void *)reset, sizeof(reset)))
return 0;
*(volatile uint32_t *)reset = 0x1;
delay(20);
*(volatile uint32_t *)reset = 0x0;
if (platform.badaddr((void *)txstat, sizeof(txstat)))
return 0;
if ((*(volatile uint32_t *)txstat & 0xff) == TXSTAT_OLDNEW)
return 1;
}
return 0;
}
static void
sq_attach(device_t parent, device_t self, void *aux)
{
int i, err;
const char* macaddr;
struct sq_softc *sc = device_private(self);
struct hpc_attach_args *haa = aux;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
sc->sc_dev = self;
sc->sc_hpct = haa->ha_st;
sc->hpc_regs = haa->hpc_regs;
if ((err = bus_space_subregion(haa->ha_st, haa->ha_sh,
haa->ha_dmaoff, sc->hpc_regs->enet_regs_size,
&sc->sc_hpch)) != 0) {
printf(": unable to map HPC DMA registers, error = %d\n", err);
goto fail_0;
}
sc->sc_regt = haa->ha_st;
if ((err = bus_space_subregion(haa->ha_st, haa->ha_sh,
haa->ha_devoff, sc->hpc_regs->enet_devregs_size,
&sc->sc_regh)) != 0) {
printf(": unable to map Seeq registers, error = %d\n", err);
goto fail_0;
}
sc->sc_dmat = haa->ha_dmat;
if ((err = bus_dmamem_alloc(sc->sc_dmat, sizeof(struct sq_control),
PAGE_SIZE, PAGE_SIZE, &sc->sc_cdseg, 1, &sc->sc_ncdseg,
BUS_DMA_NOWAIT)) != 0) {
printf(": unable to allocate control data, error = %d\n", err);
goto fail_0;
}
if ((err = bus_dmamem_map(sc->sc_dmat, &sc->sc_cdseg, sc->sc_ncdseg,
sizeof(struct sq_control), (void **)&sc->sc_control,
BUS_DMA_NOWAIT | BUS_DMA_COHERENT)) != 0) {
printf(": unable to map control data, error = %d\n", err);
goto fail_1;
}
if ((err = bus_dmamap_create(sc->sc_dmat,
sizeof(struct sq_control), 1, sizeof(struct sq_control), PAGE_SIZE,
BUS_DMA_NOWAIT, &sc->sc_cdmap)) != 0) {
printf(": unable to create DMA map for control data, error "
"= %d\n", err);
goto fail_2;
}
if ((err = bus_dmamap_load(sc->sc_dmat, sc->sc_cdmap,
sc->sc_control, sizeof(struct sq_control), NULL,
BUS_DMA_NOWAIT)) != 0) {
printf(": unable to load DMA map for control data, error "
"= %d\n", err);
goto fail_3;
}
memset(sc->sc_control, 0, sizeof(struct sq_control));
for (i = 0; i < SQ_NTXDESC; i++) {
if ((err = bus_dmamap_create(sc->sc_dmat,
MCLBYTES, 1, MCLBYTES, 0,
BUS_DMA_NOWAIT, &sc->sc_txmap[i])) != 0) {
printf(": unable to create tx DMA map %d, error = %d\n",
i, err);
goto fail_4;
}
}
for (i = 0; i < SQ_NRXDESC; i++) {
if ((err = bus_dmamap_create(sc->sc_dmat,
MCLBYTES, 1, MCLBYTES, 0,
BUS_DMA_NOWAIT, &sc->sc_rxmap[i])) != 0) {
printf(": unable to create rx DMA map %d, error = %d\n",
i, err);
goto fail_5;
}
}
for (i = 0; i < SQ_NRXDESC; i++) {
if ((err = sq_add_rxbuf(sc, i)) != 0) {
printf(": unable to allocate or map rx buffer %d\n,"
" error = %d\n", i, err);
goto fail_6;
}
}
memcpy(sc->sc_enaddr, &haa->hpc_eeprom[SQ_ONB_HPC_EEPROM_ENADDR],
ETHER_ADDR_LEN);
if (sc->sc_enaddr[0] != SGI_OUI_0 ||
sc->sc_enaddr[1] != SGI_OUI_1 ||
sc->sc_enaddr[2] != SGI_OUI_2) {
memcpy(sc->sc_enaddr,
&haa->hpc_eeprom[SQ_EXT_HPC_EEPROM_ENADDR], ETHER_ADDR_LEN);
}
if (sc->sc_enaddr[0] != SGI_OUI_0 ||
sc->sc_enaddr[1] != SGI_OUI_1 ||
sc->sc_enaddr[2] != SGI_OUI_2) {
macaddr = arcbios_GetEnvironmentVariable("eaddr");
if (macaddr == NULL) {
printf(": unable to get MAC address!\n");
goto fail_6;
}
ether_aton_r(sc->sc_enaddr, sizeof(sc->sc_enaddr), macaddr);
}
evcnt_attach_dynamic(&sc->sq_intrcnt, EVCNT_TYPE_INTR, NULL,
device_xname(self), "intr");
if ((cpu_intr_establish(haa->ha_irq, IPL_NET, sq_intr, sc)) == NULL) {
printf(": unable to establish interrupt!\n");
goto fail_6;
}
sq_reset(sc);
sq_seeq_write(sc, SEEQ_TXCOLLS0, 0xa5);
if (sq_seeq_read(sc, SEEQ_TXCOLLS0) == 0)
sc->sc_type = SQ_TYPE_80C03;
else
sc->sc_type = SQ_TYPE_8003;
sq_seeq_write(sc, SEEQ_TXCOLLS0, 0x00);
printf(": SGI Seeq %s\n",
sc->sc_type == SQ_TYPE_80C03 ? "80c03" : "8003");
printf("%s: Ethernet address %s\n",
device_xname(self), ether_sprintf(sc->sc_enaddr));
strcpy(ifp->if_xname, device_xname(self));
ifp->if_softc = sc;
ifp->if_mtu = ETHERMTU;
ifp->if_init = sq_init;
ifp->if_stop = sq_stop;
ifp->if_start = sq_start;
ifp->if_ioctl = sq_ioctl;
ifp->if_watchdog = sq_watchdog;
ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, sc->sc_enaddr);
memset(&sc->sq_trace, 0, sizeof(sc->sq_trace));
return;
fail_6:
for (i = 0; i < SQ_NRXDESC; i++) {
if (sc->sc_rxmbuf[i] != NULL) {
bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[i]);
m_freem(sc->sc_rxmbuf[i]);
}
}
fail_5:
for (i = 0; i < SQ_NRXDESC; i++) {
if (sc->sc_rxmap[i] != NULL)
bus_dmamap_destroy(sc->sc_dmat, sc->sc_rxmap[i]);
}
fail_4:
for (i = 0; i < SQ_NTXDESC; i++) {
if (sc->sc_txmap[i] != NULL)
bus_dmamap_destroy(sc->sc_dmat, sc->sc_txmap[i]);
}
bus_dmamap_unload(sc->sc_dmat, sc->sc_cdmap);
fail_3:
bus_dmamap_destroy(sc->sc_dmat, sc->sc_cdmap);
fail_2:
bus_dmamem_unmap(sc->sc_dmat,
(void *)sc->sc_control, sizeof(struct sq_control));
fail_1:
bus_dmamem_free(sc->sc_dmat, &sc->sc_cdseg, sc->sc_ncdseg);
fail_0:
return;
}
int
sq_init(struct ifnet *ifp)
{
int i;
struct sq_softc *sc = ifp->if_softc;
sq_stop(ifp, 0);
sc->sc_nextrx = 0;
sc->sc_nfreetx = SQ_NTXDESC;
sc->sc_nexttx = sc->sc_prevtx = 0;
SQ_TRACE(SQ_RESET, sc, 0, 0);
sq_seeq_write(sc, SEEQ_TXCMD, TXCMD_BANK0);
for (i = 0; i < ETHER_ADDR_LEN; i++)
sq_seeq_write(sc, i, sc->sc_enaddr[i]);
sc->sc_rxcmd =
RXCMD_IE_CRC |
RXCMD_IE_DRIB |
RXCMD_IE_SHORT |
RXCMD_IE_END |
RXCMD_IE_GOOD;
sq_set_filter(sc);
sq_seeq_write(sc, SEEQ_TXCMD,
TXCMD_IE_UFLOW |
TXCMD_IE_COLL |
TXCMD_IE_16COLL |
TXCMD_IE_GOOD);
sq_seeq_write(sc, SEEQ_RXCMD, sc->sc_rxcmd);
if (sc->hpc_regs->revision == 3) {
uint32_t dmareg, pioreg;
pioreg =
HPC3_ENETR_PIOCFG_P1(1) |
HPC3_ENETR_PIOCFG_P2(6) |
HPC3_ENETR_PIOCFG_P3(1);
dmareg =
HPC3_ENETR_DMACFG_D1(6) |
HPC3_ENETR_DMACFG_D2(2) |
HPC3_ENETR_DMACFG_D3(0) |
HPC3_ENETR_DMACFG_FIX_RXDC |
HPC3_ENETR_DMACFG_FIX_INTR |
HPC3_ENETR_DMACFG_FIX_EOP |
HPC3_ENETR_DMACFG_TIMEOUT;
sq_hpc_write(sc, HPC3_ENETR_PIOCFG, pioreg);
sq_hpc_write(sc, HPC3_ENETR_DMACFG, dmareg);
}
sq_hpc_write(sc, sc->hpc_regs->enetr_ndbp, SQ_CDRXADDR(sc, 0));
sq_hpc_write(sc, sc->hpc_regs->enetr_ctl,
sc->hpc_regs->enetr_ctl_active);
if (sc->hpc_regs->revision != 3)
sq_hpc_write(sc, HPC1_ENET_INTDELAY, HPC1_ENET_INTDELAY_OFF);
ifp->if_flags |= IFF_RUNNING;
return 0;
}
static void
sq_set_filter(struct sq_softc *sc)
{
struct ethercom *ec = &sc->sc_ethercom;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct ether_multi *enm;
struct ether_multistep step;
if (ifp->if_flags & IFF_PROMISC) {
sc->sc_rxcmd |= RXCMD_REC_ALL;
ifp->if_flags |= IFF_ALLMULTI;
return;
}
ETHER_FIRST_MULTI(step, ec, enm);
if (enm == NULL) {
sc->sc_rxcmd &= ~RXCMD_REC_MASK;
sc->sc_rxcmd |= RXCMD_REC_BROAD;
ifp->if_flags &= ~IFF_ALLMULTI;
return;
}
sc->sc_rxcmd |= RXCMD_REC_MULTI;
ifp->if_flags |= IFF_ALLMULTI;
}
int
sq_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
int s, error = 0;
SQ_TRACE(SQ_IOCTL, (struct sq_softc *)ifp->if_softc, 0, 0);
s = splnet();
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
error = sq_init(ifp);
else
error = 0;
}
splx(s);
return error;
}
void
sq_start(struct ifnet *ifp)
{
struct sq_softc *sc = ifp->if_softc;
uint32_t status;
struct mbuf *m0, *m;
bus_dmamap_t dmamap;
int err, totlen, nexttx, firsttx, lasttx = -1, ofree, seg;
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
ofree = sc->sc_nfreetx;
firsttx = sc->sc_nexttx;
while (sc->sc_nfreetx != 0) {
IFQ_POLL(&ifp->if_snd, m0);
if (m0 == NULL)
break;
m = NULL;
dmamap = sc->sc_txmap[sc->sc_nexttx];
if (m0->m_pkthdr.len < ETHER_PAD_LEN ||
bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0,
BUS_DMA_NOWAIT) != 0) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL) {
printf("%s: unable to allocate Tx mbuf\n",
device_xname(sc->sc_dev));
break;
}
if (m0->m_pkthdr.len > MHLEN) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
printf("%s: unable to allocate Tx "
"cluster\n",
device_xname(sc->sc_dev));
m_freem(m);
break;
}
}
m_copydata(m0, 0, m0->m_pkthdr.len, mtod(m, void *));
if (m0->m_pkthdr.len < ETHER_PAD_LEN) {
memset(mtod(m, char *) + m0->m_pkthdr.len, 0,
ETHER_PAD_LEN - m0->m_pkthdr.len);
m->m_pkthdr.len = m->m_len = ETHER_PAD_LEN;
} else
m->m_pkthdr.len = m->m_len = m0->m_pkthdr.len;
if ((err = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap,
m, BUS_DMA_NOWAIT)) != 0) {
printf("%s: unable to load Tx buffer, "
"error = %d\n",
device_xname(sc->sc_dev), err);
break;
}
}
if (dmamap->dm_nsegs > sc->sc_nfreetx) {
bus_dmamap_unload(sc->sc_dmat, dmamap);
m_freem(m);
break;
}
IFQ_DEQUEUE(&ifp->if_snd, m0);
bpf_mtap(ifp, m0, BPF_D_OUT);
if (m != NULL) {
m_freem(m0);
m0 = m;
}
SQ_TRACE(SQ_ENQUEUE, sc, sc->sc_nexttx, 0);
bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
BUS_DMASYNC_PREWRITE);
for (nexttx = sc->sc_nexttx, seg = 0, totlen = 0;
seg < dmamap->dm_nsegs;
seg++, nexttx = SQ_NEXTTX(nexttx)) {
if (sc->hpc_regs->revision == 3) {
sc->sc_txdesc[nexttx].hpc3_hdd_bufptr =
dmamap->dm_segs[seg].ds_addr;
sc->sc_txdesc[nexttx].hpc3_hdd_ctl =
dmamap->dm_segs[seg].ds_len;
} else {
sc->sc_txdesc[nexttx].hpc1_hdd_bufptr =
dmamap->dm_segs[seg].ds_addr;
sc->sc_txdesc[nexttx].hpc1_hdd_ctl =
dmamap->dm_segs[seg].ds_len;
}
sc->sc_txdesc[nexttx].hdd_descptr =
SQ_CDTXADDR(sc, SQ_NEXTTX(nexttx));
lasttx = nexttx;
totlen += dmamap->dm_segs[seg].ds_len;
}
KASSERT(lasttx != -1);
if (sc->hpc_regs->revision == 3)
sc->sc_txdesc[lasttx].hpc3_hdd_ctl |=
HPC3_HDD_CTL_EOPACKET;
else
sc->sc_txdesc[lasttx].hpc1_hdd_ctl |=
HPC1_HDD_CTL_EOPACKET;
SQ_DPRINTF(("%s: transmit %d-%d, len %d\n",
device_xname(sc->sc_dev), sc->sc_nexttx, lasttx, totlen));
if (ifp->if_flags & IFF_DEBUG) {
printf(" transmit chain:\n");
for (seg = sc->sc_nexttx;; seg = SQ_NEXTTX(seg)) {
printf(" descriptor %d:\n", seg);
printf(" hdd_bufptr: 0x%08x\n",
(sc->hpc_regs->revision == 3) ?
sc->sc_txdesc[seg].hpc3_hdd_bufptr :
sc->sc_txdesc[seg].hpc1_hdd_bufptr);
printf(" hdd_ctl: 0x%08x\n",
(sc->hpc_regs->revision == 3) ?
sc->sc_txdesc[seg].hpc3_hdd_ctl:
sc->sc_txdesc[seg].hpc1_hdd_ctl);
printf(" hdd_descptr: 0x%08x\n",
sc->sc_txdesc[seg].hdd_descptr);
if (seg == lasttx)
break;
}
}
SQ_CDTXSYNC(sc, sc->sc_nexttx, dmamap->dm_nsegs,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
sc->sc_txmbuf[sc->sc_nexttx] = m0;
sc->sc_nfreetx -= dmamap->dm_nsegs;
sc->sc_nexttx = nexttx;
}
if (sc->sc_nfreetx != ofree) {
SQ_DPRINTF(("%s: %d packets enqueued, first %d, INTR on %d\n",
device_xname(sc->sc_dev), lasttx - firsttx + 1,
firsttx, lasttx));
KASSERT(lasttx != -1);
if (sc->hpc_regs->revision == 3) {
sc->sc_txdesc[lasttx].hpc3_hdd_ctl |=
HPC3_HDD_CTL_INTR | HPC3_HDD_CTL_EOCHAIN;
} else {
sc->sc_txdesc[lasttx].hpc1_hdd_ctl |= HPC1_HDD_CTL_INTR;
sc->sc_txdesc[lasttx].hpc1_hdd_bufptr |=
HPC1_HDD_CTL_EOCHAIN;
}
SQ_CDTXSYNC(sc, lasttx, 1,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
status = sq_hpc_read(sc, sc->hpc_regs->enetx_ctl);
if ((status & sc->hpc_regs->enetx_ctl_active) != 0) {
SQ_TRACE(SQ_ADD_TO_DMA, sc, firsttx, status);
sc->sc_txdesc[SQ_PREVTX(firsttx)].hpc3_hdd_ctl &=
~HPC3_HDD_CTL_EOCHAIN;
if (sc->hpc_regs->revision != 3)
sc->sc_txdesc[SQ_PREVTX(firsttx)].hpc1_hdd_ctl
&= ~HPC1_HDD_CTL_INTR;
SQ_CDTXSYNC(sc, SQ_PREVTX(firsttx), 1,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
} else if (sc->hpc_regs->revision == 3) {
SQ_TRACE(SQ_START_DMA, sc, firsttx, status);
sq_hpc_write(sc, HPC3_ENETX_NDBP, SQ_CDTXADDR(sc,
firsttx));
sq_hpc_write(sc, HPC3_ENETX_CTL, HPC3_ENETX_CTL_ACTIVE);
} else {
if (ofree == SQ_NTXDESC) {
SQ_TRACE(SQ_START_DMA, sc, firsttx, status);
sq_hpc_write(sc, HPC1_ENETX_NDBP,
SQ_CDTXADDR(sc, firsttx));
sq_hpc_write(sc, HPC1_ENETX_CFXBP,
SQ_CDTXADDR(sc, firsttx));
sq_hpc_write(sc, HPC1_ENETX_CBP,
SQ_CDTXADDR(sc, firsttx));
sq_hpc_write(sc, HPC1_ENETX_CTL,
HPC1_ENETX_CTL_ACTIVE);
} else
sq_txring_hpc1(sc);
}
ifp->if_timer = 5;
}
}
void
sq_stop(struct ifnet *ifp, int disable)
{
int i;
struct sq_softc *sc = ifp->if_softc;
for (i = 0; i < SQ_NTXDESC; i++) {
if (sc->sc_txmbuf[i] != NULL) {
bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[i]);
m_freem(sc->sc_txmbuf[i]);
sc->sc_txmbuf[i] = NULL;
}
}
sq_seeq_write(sc, SEEQ_TXCMD, 0);
sq_seeq_write(sc, SEEQ_RXCMD, 0);
sq_reset(sc);
ifp->if_flags &= ~IFF_RUNNING;
ifp->if_timer = 0;
}
void
sq_watchdog(struct ifnet *ifp)
{
uint32_t status;
struct sq_softc *sc = ifp->if_softc;
status = sq_hpc_read(sc, sc->hpc_regs->enetx_ctl);
log(LOG_ERR, "%s: device timeout (prev %d, next %d, free %d, "
"status %08x)\n", device_xname(sc->sc_dev), sc->sc_prevtx,
sc->sc_nexttx, sc->sc_nfreetx, status);
sq_trace_dump(sc);
memset(&sc->sq_trace, 0, sizeof(sc->sq_trace));
sc->sq_trace_idx = 0;
if_statinc(ifp, if_oerrors);
sq_init(ifp);
}
static void
sq_trace_dump(struct sq_softc *sc)
{
int i;
const char *act;
for (i = 0; i < sc->sq_trace_idx; i++) {
switch (sc->sq_trace[i].action) {
case SQ_RESET: act = "SQ_RESET"; break;
case SQ_ADD_TO_DMA: act = "SQ_ADD_TO_DMA"; break;
case SQ_START_DMA: act = "SQ_START_DMA"; break;
case SQ_DONE_DMA: act = "SQ_DONE_DMA"; break;
case SQ_RESTART_DMA: act = "SQ_RESTART_DMA"; break;
case SQ_TXINTR_ENTER: act = "SQ_TXINTR_ENTER"; break;
case SQ_TXINTR_EXIT: act = "SQ_TXINTR_EXIT"; break;
case SQ_TXINTR_BUSY: act = "SQ_TXINTR_BUSY"; break;
case SQ_IOCTL: act = "SQ_IOCTL"; break;
case SQ_ENQUEUE: act = "SQ_ENQUEUE"; break;
default: act = "UNKNOWN";
}
printf("%s: [%03d] action %-16s buf %03d free %03d "
"status %08x line %d\n", device_xname(sc->sc_dev), i, act,
sc->sq_trace[i].bufno, sc->sq_trace[i].freebuf,
sc->sq_trace[i].status, sc->sq_trace[i].line);
}
}
static int
sq_intr(void *arg)
{
struct sq_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
int handled = 0;
uint32_t stat;
stat = sq_hpc_read(sc, sc->hpc_regs->enetr_reset);
if ((stat & 2) == 0) {
SQ_DPRINTF(("%s: Unexpected interrupt!\n",
device_xname(sc->sc_dev)));
} else
sq_hpc_write(sc, sc->hpc_regs->enetr_reset, (stat | 2));
if ((ifp->if_flags & IFF_RUNNING) == 0)
return 0;
sc->sq_intrcnt.ev_count++;
if (sq_rxintr(sc) != 0)
handled++;
if (sc->sc_nfreetx < SQ_NTXDESC) {
sq_txintr(sc);
handled++;
}
if (handled)
rnd_add_uint32(&sc->rnd_source, stat);
return handled;
}
static int
sq_rxintr(struct sq_softc *sc)
{
int count = 0;
struct mbuf* m;
int i, framelen;
uint8_t pktstat;
uint32_t status;
uint32_t ctl_reg;
int new_end, orig_end;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
for (i = sc->sc_nextrx;; i = SQ_NEXTRX(i)) {
SQ_CDRXSYNC(sc, i,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
if (sc->hpc_regs->revision == 3)
ctl_reg =
sc->sc_rxdesc[i].hpc3_hdd_ctl & HPC3_HDD_CTL_OWN;
else
ctl_reg =
sc->sc_rxdesc[i].hpc1_hdd_ctl & HPC1_HDD_CTL_OWN;
if (ctl_reg) {
#if defined(SQ_DEBUG)
uint32_t reg;
reg = sq_hpc_read(sc, sc->hpc_regs->enetr_ctl);
SQ_DPRINTF(("%s: rxintr: done at %d (ctl %08x)\n",
device_xname(sc->sc_dev), i, reg));
#endif
break;
}
count++;
m = sc->sc_rxmbuf[i];
framelen = m->m_ext.ext_size - 3;
if (sc->hpc_regs->revision == 3)
framelen -=
HPC3_HDD_CTL_BYTECNT(sc->sc_rxdesc[i].hpc3_hdd_ctl);
else
framelen -=
HPC1_HDD_CTL_BYTECNT(sc->sc_rxdesc[i].hpc1_hdd_ctl);
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[i], 0,
sc->sc_rxmap[i]->dm_mapsize, BUS_DMASYNC_POSTREAD);
pktstat = *((uint8_t *)m->m_data + framelen + 2);
if ((pktstat & RXSTAT_GOOD) == 0) {
if_statinc(ifp, if_ierrors);
if (pktstat & RXSTAT_OFLOW)
printf("%s: receive FIFO overflow\n",
device_xname(sc->sc_dev));
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[i], 0,
sc->sc_rxmap[i]->dm_mapsize, BUS_DMASYNC_PREREAD);
SQ_INIT_RXDESC(sc, i);
SQ_DPRINTF(("%s: sq_rxintr: buf %d no RXSTAT_GOOD\n",
device_xname(sc->sc_dev), i));
continue;
}
if (sq_add_rxbuf(sc, i) != 0) {
if_statinc(ifp, if_ierrors);
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[i], 0,
sc->sc_rxmap[i]->dm_mapsize, BUS_DMASYNC_PREREAD);
SQ_INIT_RXDESC(sc, i);
SQ_DPRINTF(("%s: sq_rxintr: buf %d sq_add_rxbuf() "
"failed\n", device_xname(sc->sc_dev), i));
continue;
}
m->m_data += 2;
m_set_rcvif(m, ifp);
m->m_pkthdr.len = m->m_len = framelen;
SQ_DPRINTF(("%s: sq_rxintr: buf %d len %d\n",
device_xname(sc->sc_dev), i, framelen));
if_percpuq_enqueue(ifp->if_percpuq, m);
}
if (i != sc->sc_nextrx) {
new_end = SQ_PREVRX(i);
sc->sc_rxdesc[new_end].hpc3_hdd_ctl |= HPC3_HDD_CTL_EOCHAIN;
SQ_CDRXSYNC(sc, new_end,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
orig_end = SQ_PREVRX(sc->sc_nextrx);
sc->sc_rxdesc[orig_end].hpc3_hdd_ctl &= ~HPC3_HDD_CTL_EOCHAIN;
SQ_CDRXSYNC(sc, orig_end,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
sc->sc_nextrx = i;
}
status = sq_hpc_read(sc, sc->hpc_regs->enetr_ctl);
if ((status & sc->hpc_regs->enetr_ctl_active) == 0) {
sq_hpc_write(sc, sc->hpc_regs->enetr_ndbp,
SQ_CDRXADDR(sc, sc->sc_nextrx));
sq_hpc_write(sc, sc->hpc_regs->enetr_ctl,
sc->hpc_regs->enetr_ctl_active);
}
return count;
}
static int
sq_txintr(struct sq_softc *sc)
{
int shift = 0;
uint32_t status, tmp;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
if (sc->hpc_regs->revision != 3)
shift = 16;
status = sq_hpc_read(sc, sc->hpc_regs->enetx_ctl) >> shift;
SQ_TRACE(SQ_TXINTR_ENTER, sc, sc->sc_prevtx, status);
net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
tmp = (sc->hpc_regs->enetx_ctl_active >> shift) | TXSTAT_GOOD;
if ((status & tmp) == 0) {
if (status & TXSTAT_COLL)
if_statinc_ref(ifp, nsr, if_collisions);
if (status & TXSTAT_UFLOW) {
printf("%s: transmit underflow\n",
device_xname(sc->sc_dev));
if_statinc_ref(ifp, nsr, if_oerrors);
}
if (status & TXSTAT_16COLL) {
printf("%s: max collisions reached\n",
device_xname(sc->sc_dev));
if_statinc_ref(ifp, nsr, if_oerrors);
if_statadd_ref(ifp, nsr, if_collisions, 16);
}
}
IF_STAT_PUTREF(ifp);
if (sc->hpc_regs->revision == 3)
sq_txring_hpc3(sc);
else
sq_txring_hpc1(sc);
if (sc->sc_nfreetx == SQ_NTXDESC)
ifp->if_timer = 0;
SQ_TRACE(SQ_TXINTR_EXIT, sc, sc->sc_prevtx, status);
if_schedule_deferred_start(ifp);
return 1;
}
static void
sq_txring_hpc1(struct sq_softc *sc)
{
uint32_t reclaimto, status;
int reclaimall, i = sc->sc_prevtx;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
status = sq_hpc_read(sc, HPC1_ENETX_CTL);
if (status & HPC1_ENETX_CTL_ACTIVE) {
SQ_TRACE(SQ_TXINTR_BUSY, sc, i, status);
return;
} else
reclaimto = sq_hpc_read(sc, HPC1_ENETX_NDBP);
if (sc->sc_nfreetx == 0 && SQ_CDTXADDR(sc, i) == reclaimto)
reclaimall = 1;
else
reclaimall = 0;
while (sc->sc_nfreetx < SQ_NTXDESC) {
if (SQ_CDTXADDR(sc, i) == reclaimto && !reclaimall)
break;
SQ_CDTXSYNC(sc, i, sc->sc_txmap[i]->dm_nsegs,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[i],
0, sc->sc_txmap[i]->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[i]);
m_freem(sc->sc_txmbuf[i]);
sc->sc_txmbuf[i] = NULL;
if_statinc(ifp, if_opackets);
sc->sc_nfreetx++;
SQ_TRACE(SQ_DONE_DMA, sc, i, status);
i = SQ_NEXTTX(i);
}
if (sc->sc_nfreetx < SQ_NTXDESC) {
SQ_TRACE(SQ_RESTART_DMA, sc, i, status);
KASSERT(reclaimto == SQ_CDTXADDR(sc, i));
sq_hpc_write(sc, HPC1_ENETX_CFXBP, reclaimto);
sq_hpc_write(sc, HPC1_ENETX_CBP, reclaimto);
sq_hpc_write(sc, HPC1_ENETX_CTL, HPC1_ENETX_CTL_ACTIVE);
ifp->if_timer = 5;
}
sc->sc_prevtx = i;
}
static void
sq_txring_hpc3(struct sq_softc *sc)
{
int i;
uint32_t status = 0;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
i = sc->sc_prevtx;
while (sc->sc_nfreetx < SQ_NTXDESC) {
status = sq_hpc_read(sc, HPC3_ENETX_CTL);
SQ_CDTXSYNC(sc, i, sc->sc_txmap[i]->dm_nsegs,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
if ((sc->sc_txdesc[i].hpc3_hdd_ctl &
HPC3_HDD_CTL_XMITDONE) == 0) {
if ((status & HPC3_ENETX_CTL_ACTIVE) == 0) {
SQ_TRACE(SQ_RESTART_DMA, sc, i, status);
sq_hpc_write(sc, HPC3_ENETX_NDBP,
SQ_CDTXADDR(sc, i));
sq_hpc_write(sc, HPC3_ENETX_CTL,
HPC3_ENETX_CTL_ACTIVE);
ifp->if_timer = 5;
} else
SQ_TRACE(SQ_TXINTR_BUSY, sc, i, status);
break;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[i],
0, sc->sc_txmap[i]->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[i]);
m_freem(sc->sc_txmbuf[i]);
sc->sc_txmbuf[i] = NULL;
if_statinc(ifp, if_opackets);
sc->sc_nfreetx++;
SQ_TRACE(SQ_DONE_DMA, sc, i, status);
i = SQ_NEXTTX(i);
}
sc->sc_prevtx = i;
}
void
sq_reset(struct sq_softc *sc)
{
sq_hpc_write(sc, sc->hpc_regs->enetr_ctl, 0);
sq_hpc_write(sc, sc->hpc_regs->enetx_ctl, 0);
sq_hpc_write(sc, sc->hpc_regs->enetr_reset, 3);
delay(20);
sq_hpc_write(sc, sc->hpc_regs->enetr_reset, 0);
}
int
sq_add_rxbuf(struct sq_softc *sc, int idx)
{
int err;
struct mbuf *m;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
return ENOBUFS;
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
return ENOBUFS;
}
if (sc->sc_rxmbuf[idx] != NULL)
bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[idx]);
sc->sc_rxmbuf[idx] = m;
if ((err = bus_dmamap_load(sc->sc_dmat, sc->sc_rxmap[idx],
m->m_ext.ext_buf, m->m_ext.ext_size, NULL, BUS_DMA_NOWAIT)) != 0) {
printf("%s: can't load rx DMA map %d, error = %d\n",
device_xname(sc->sc_dev), idx, err);
panic("sq_add_rxbuf");
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx],
0, sc->sc_rxmap[idx]->dm_mapsize, BUS_DMASYNC_PREREAD);
SQ_INIT_RXDESC(sc, idx);
return 0;
}
void
sq_dump_buffer(paddr_t addr, psize_t len)
{
u_int i;
uint8_t *physaddr = (uint8_t *)MIPS_PHYS_TO_KSEG1(addr);
if (len == 0)
return;
printf("%p: ", physaddr);
for (i = 0; i < len; i++) {
printf("%02x ", *(physaddr + i) & 0xff);
if ((i % 16) == 15 && i != len - 1)
printf("\n%p: ", physaddr + i);
}
printf("\n");
}