#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dp83932.c,v 1.50 2024/06/29 12:11:11 riastradh Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/rndsource.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#include <sys/bus.h>
#include <sys/intr.h>
#include <dev/ic/dp83932reg.h>
#include <dev/ic/dp83932var.h>
static void sonic_start(struct ifnet *);
static void sonic_watchdog(struct ifnet *);
static int sonic_ioctl(struct ifnet *, u_long, void *);
static int sonic_init(struct ifnet *);
static void sonic_stop(struct ifnet *, int);
static bool sonic_shutdown(device_t, int);
static void sonic_reset(struct sonic_softc *);
static void sonic_rxdrain(struct sonic_softc *);
static int sonic_add_rxbuf(struct sonic_softc *, int);
static void sonic_set_filter(struct sonic_softc *);
static uint16_t sonic_txintr(struct sonic_softc *);
static void sonic_rxintr(struct sonic_softc *);
int sonic_copy_small = 0;
#define ETHER_PAD_LEN (ETHER_MIN_LEN - ETHER_CRC_LEN)
void
sonic_attach(struct sonic_softc *sc, const uint8_t *enaddr)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
int i, rseg, error;
bus_dma_segment_t seg;
size_t cdatasize;
uint8_t *nullbuf;
if (sc->sc_32bit)
cdatasize = sizeof(struct sonic_control_data32);
else
cdatasize = sizeof(struct sonic_control_data16);
if ((error = bus_dmamem_alloc(sc->sc_dmat, cdatasize + ETHER_PAD_LEN,
PAGE_SIZE, (64 * 1024), &seg, 1, &rseg,
BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to allocate control data, error = %d\n", error);
goto fail_0;
}
if ((error = bus_dmamem_map(sc->sc_dmat, &seg, rseg,
cdatasize + ETHER_PAD_LEN, (void **) &sc->sc_cdata16,
BUS_DMA_NOWAIT | BUS_DMA_COHERENT)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to map control data, error = %d\n", error);
goto fail_1;
}
nullbuf = (uint8_t *)sc->sc_cdata16 + cdatasize;
memset(nullbuf, 0, ETHER_PAD_LEN);
if ((error = bus_dmamap_create(sc->sc_dmat,
cdatasize, 1, cdatasize, 0, BUS_DMA_NOWAIT,
&sc->sc_cddmamap)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to create control data DMA map, error = %d\n",
error);
goto fail_2;
}
if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_cddmamap,
sc->sc_cdata16, cdatasize, NULL, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to load control data DMA map, error = %d\n", error);
goto fail_3;
}
for (i = 0; i < SONIC_NTXDESC; i++) {
if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES,
SONIC_NTXFRAGS, MCLBYTES, 0, BUS_DMA_NOWAIT,
&sc->sc_txsoft[i].ds_dmamap)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to create tx DMA map %d, error = %d\n",
i, error);
goto fail_4;
}
}
for (i = 0; i < SONIC_NRXDESC; i++) {
if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
MCLBYTES, 0, BUS_DMA_NOWAIT,
&sc->sc_rxsoft[i].ds_dmamap)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to create rx DMA map %d, error = %d\n",
i, error);
goto fail_5;
}
sc->sc_rxsoft[i].ds_mbuf = NULL;
}
if ((error = bus_dmamap_create(sc->sc_dmat, ETHER_PAD_LEN, 1,
ETHER_PAD_LEN, 0, BUS_DMA_NOWAIT, &sc->sc_nulldmamap)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to create pad buffer DMA map, error = %d\n", error);
goto fail_5;
}
if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_nulldmamap,
nullbuf, ETHER_PAD_LEN, NULL, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"unable to load pad buffer DMA map, error = %d\n", error);
goto fail_6;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_nulldmamap, 0, ETHER_PAD_LEN,
BUS_DMASYNC_PREWRITE);
sonic_reset(sc);
aprint_normal_dev(sc->sc_dev, "Ethernet address %s\n",
ether_sprintf(enaddr));
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_ioctl = sonic_ioctl;
ifp->if_start = sonic_start;
ifp->if_watchdog = sonic_watchdog;
ifp->if_init = sonic_init;
ifp->if_stop = sonic_stop;
IFQ_SET_READY(&ifp->if_snd);
sc->sc_ethercom.ec_capabilities |= ETHERCAP_VLAN_MTU;
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, enaddr);
rnd_attach_source(&sc->sc_rndsource, ifp->if_xname, RND_TYPE_NET,
RND_FLAG_DEFAULT);
if (pmf_device_register1(sc->sc_dev, NULL, NULL, sonic_shutdown))
pmf_class_network_register(sc->sc_dev, ifp);
else
aprint_error_dev(sc->sc_dev,
"couldn't establish power handler\n");
return;
fail_6:
bus_dmamap_destroy(sc->sc_dmat, sc->sc_nulldmamap);
fail_5:
for (i = 0; i < SONIC_NRXDESC; i++) {
if (sc->sc_rxsoft[i].ds_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->sc_rxsoft[i].ds_dmamap);
}
fail_4:
for (i = 0; i < SONIC_NTXDESC; i++) {
if (sc->sc_txsoft[i].ds_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->sc_txsoft[i].ds_dmamap);
}
bus_dmamap_unload(sc->sc_dmat, sc->sc_cddmamap);
fail_3:
bus_dmamap_destroy(sc->sc_dmat, sc->sc_cddmamap);
fail_2:
bus_dmamem_unmap(sc->sc_dmat, (void *)sc->sc_cdata16, cdatasize);
fail_1:
bus_dmamem_free(sc->sc_dmat, &seg, rseg);
fail_0:
return;
}
bool
sonic_shutdown(device_t self, int howto)
{
struct sonic_softc *sc = device_private(self);
sonic_stop(&sc->sc_ethercom.ec_if, 1);
return true;
}
void
sonic_start(struct ifnet *ifp)
{
struct sonic_softc *sc = ifp->if_softc;
struct mbuf *m0, *m;
struct sonic_tda16 *tda16;
struct sonic_tda32 *tda32;
struct sonic_descsoft *ds;
bus_dmamap_t dmamap;
int error, olasttx, nexttx, opending, totlen, olseg;
int seg = 0;
if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING)
return;
opending = sc->sc_txpending;
olasttx = sc->sc_txlast;
while (sc->sc_txpending < (SONIC_NTXDESC - 1)) {
IFQ_POLL(&ifp->if_snd, m0);
if (m0 == NULL)
break;
m = NULL;
nexttx = SONIC_NEXTTX(sc->sc_txlast);
ds = &sc->sc_txsoft[nexttx];
dmamap = ds->ds_dmamap;
if ((error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0,
BUS_DMA_WRITE | BUS_DMA_NOWAIT)) != 0 ||
(m0->m_pkthdr.len < ETHER_PAD_LEN &&
dmamap->dm_nsegs == SONIC_NTXFRAGS)) {
if (error == 0)
bus_dmamap_unload(sc->sc_dmat, dmamap);
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 *));
m->m_pkthdr.len = m->m_len = m0->m_pkthdr.len;
error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap,
m, BUS_DMA_WRITE | BUS_DMA_NOWAIT);
if (error) {
printf("%s: unable to load Tx buffer, "
"error = %d\n", device_xname(sc->sc_dev),
error);
m_freem(m);
break;
}
}
IFQ_DEQUEUE(&ifp->if_snd, m0);
if (m != NULL) {
m_freem(m0);
m0 = m;
}
bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
BUS_DMASYNC_PREWRITE);
ds->ds_mbuf = m0;
totlen = 0;
if (sc->sc_32bit) {
tda32 = &sc->sc_tda32[nexttx];
for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
tda32->tda_frags[seg].frag_ptr1 =
htosonic32(sc,
(dmamap->dm_segs[seg].ds_addr >> 16) &
0xffff);
tda32->tda_frags[seg].frag_ptr0 =
htosonic32(sc,
dmamap->dm_segs[seg].ds_addr & 0xffff);
tda32->tda_frags[seg].frag_size =
htosonic32(sc, dmamap->dm_segs[seg].ds_len);
totlen += dmamap->dm_segs[seg].ds_len;
}
if (totlen < ETHER_PAD_LEN) {
tda32->tda_frags[seg].frag_ptr1 =
htosonic32(sc,
(sc->sc_nulldma >> 16) & 0xffff);
tda32->tda_frags[seg].frag_ptr0 =
htosonic32(sc, sc->sc_nulldma & 0xffff);
tda32->tda_frags[seg].frag_size =
htosonic32(sc, ETHER_PAD_LEN - totlen);
totlen = ETHER_PAD_LEN;
seg++;
}
tda32->tda_status = 0;
tda32->tda_pktconfig = 0;
tda32->tda_pktsize = htosonic32(sc, totlen);
tda32->tda_fragcnt = htosonic32(sc, seg);
tda32->tda_frags[seg].frag_ptr0 =
htosonic32(sc, SONIC_CDTXADDR32(sc,
SONIC_NEXTTX(nexttx)) & 0xffff);
SONIC_CDTXSYNC32(sc, nexttx,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
} else {
tda16 = &sc->sc_tda16[nexttx];
for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
tda16->tda_frags[seg].frag_ptr1 =
htosonic16(sc,
(dmamap->dm_segs[seg].ds_addr >> 16) &
0xffff);
tda16->tda_frags[seg].frag_ptr0 =
htosonic16(sc,
dmamap->dm_segs[seg].ds_addr & 0xffff);
tda16->tda_frags[seg].frag_size =
htosonic16(sc, dmamap->dm_segs[seg].ds_len);
totlen += dmamap->dm_segs[seg].ds_len;
}
if (totlen < ETHER_PAD_LEN) {
tda16->tda_frags[seg].frag_ptr1 =
htosonic16(sc,
(sc->sc_nulldma >> 16) & 0xffff);
tda16->tda_frags[seg].frag_ptr0 =
htosonic16(sc, sc->sc_nulldma & 0xffff);
tda16->tda_frags[seg].frag_size =
htosonic16(sc, ETHER_PAD_LEN - totlen);
totlen = ETHER_PAD_LEN;
seg++;
}
tda16->tda_status = 0;
tda16->tda_pktconfig = 0;
tda16->tda_pktsize = htosonic16(sc, totlen);
tda16->tda_fragcnt = htosonic16(sc, seg);
tda16->tda_frags[seg].frag_ptr0 =
htosonic16(sc, SONIC_CDTXADDR16(sc,
SONIC_NEXTTX(nexttx)) & 0xffff);
SONIC_CDTXSYNC16(sc, nexttx,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
sc->sc_txpending++;
sc->sc_txlast = nexttx;
bpf_mtap(ifp, m0, BPF_D_OUT);
}
if (sc->sc_txpending != opending) {
if (opending == 0)
sc->sc_txdirty = SONIC_NEXTTX(olasttx);
if (sc->sc_32bit) {
olseg =
sonic32toh(sc, sc->sc_tda32[olasttx].tda_fragcnt);
sc->sc_tda32[sc->sc_txlast].tda_frags[seg].frag_ptr0 |=
htosonic32(sc, TDA_LINK_EOL);
SONIC_CDTXSYNC32(sc, sc->sc_txlast,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
sc->sc_tda32[olasttx].tda_frags[olseg].frag_ptr0 &=
htosonic32(sc, ~TDA_LINK_EOL);
SONIC_CDTXSYNC32(sc, olasttx,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
} else {
olseg =
sonic16toh(sc, sc->sc_tda16[olasttx].tda_fragcnt);
sc->sc_tda16[sc->sc_txlast].tda_frags[seg].frag_ptr0 |=
htosonic16(sc, TDA_LINK_EOL);
SONIC_CDTXSYNC16(sc, sc->sc_txlast,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
sc->sc_tda16[olasttx].tda_frags[olseg].frag_ptr0 &=
htosonic16(sc, ~TDA_LINK_EOL);
SONIC_CDTXSYNC16(sc, olasttx,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
CSR_WRITE(sc, SONIC_CR, CR_TXP);
ifp->if_timer = 5;
}
}
void
sonic_watchdog(struct ifnet *ifp)
{
struct sonic_softc *sc = ifp->if_softc;
printf("%s: device timeout\n", device_xname(sc->sc_dev));
if_statinc(ifp, if_oerrors);
(void)sonic_init(ifp);
}
int
sonic_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
int s, error;
s = splnet();
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
(void)sonic_init(ifp);
error = 0;
}
splx(s);
return error;
}
int
sonic_intr(void *arg)
{
struct sonic_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
uint16_t isr;
int handled = 0, wantinit;
for (wantinit = 0; wantinit == 0;) {
isr = CSR_READ(sc, SONIC_ISR) & sc->sc_imr;
if (isr == 0)
break;
CSR_WRITE(sc, SONIC_ISR, isr);
handled = 1;
if (isr & IMR_PRX)
sonic_rxintr(sc);
if (isr & (IMR_PTX | IMR_TXER)) {
if (sonic_txintr(sc) & TCR_FU) {
printf("%s: transmit FIFO underrun\n",
device_xname(sc->sc_dev));
wantinit = 1;
}
}
if (isr & (IMR_RFO | IMR_RBA | IMR_RBE | IMR_RDE)) {
#define PRINTERR(bit, str) \
if (isr & (bit)) \
printf("%s: %s\n",device_xname(sc->sc_dev), str)
PRINTERR(IMR_RFO, "receive FIFO overrun");
PRINTERR(IMR_RBA, "receive buffer exceeded");
PRINTERR(IMR_RBE, "receive buffers exhausted");
PRINTERR(IMR_RDE, "receive descriptors exhausted");
wantinit = 1;
}
}
if (handled) {
if (wantinit)
(void)sonic_init(ifp);
if_schedule_deferred_start(ifp);
}
return handled;
}
uint16_t
sonic_txintr(struct sonic_softc *sc)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct sonic_descsoft *ds;
struct sonic_tda32 *tda32;
struct sonic_tda16 *tda16;
uint16_t status, totstat = 0;
int i, count;
count = 0;
for (i = sc->sc_txdirty; sc->sc_txpending != 0;
i = SONIC_NEXTTX(i), sc->sc_txpending--) {
ds = &sc->sc_txsoft[i];
if (sc->sc_32bit) {
SONIC_CDTXSYNC32(sc, i,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
tda32 = &sc->sc_tda32[i];
status = sonic32toh(sc, tda32->tda_status);
SONIC_CDTXSYNC32(sc, i, BUS_DMASYNC_PREREAD);
} else {
SONIC_CDTXSYNC16(sc, i,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
tda16 = &sc->sc_tda16[i];
status = sonic16toh(sc, tda16->tda_status);
SONIC_CDTXSYNC16(sc, i, BUS_DMASYNC_PREREAD);
}
if ((status & ~(TCR_EXDIS |TCR_CRCI |TCR_POWC |TCR_PINT)) == 0)
break;
totstat |= status;
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
m_freem(ds->ds_mbuf);
ds->ds_mbuf = NULL;
net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
if (status & TCR_PTX) {
if_statinc_ref(ifp, nsr, if_opackets);
count++;
} else {
if_statinc_ref(ifp, nsr, if_oerrors);
}
if (TDA_STATUS_NCOL(status)) {
if_statadd_ref(ifp, nsr, if_collisions,
TDA_STATUS_NCOL(status));
}
IF_STAT_PUTREF(ifp);
}
sc->sc_txdirty = i;
if (sc->sc_txpending == 0)
ifp->if_timer = 0;
if (totstat != 0)
rnd_add_uint32(&sc->sc_rndsource, totstat);
return totstat;
}
void
sonic_rxintr(struct sonic_softc *sc)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct sonic_descsoft *ds;
struct sonic_rda32 *rda32;
struct sonic_rda16 *rda16;
struct mbuf *m;
int i, len, count;
uint16_t status, bytecount ;
count = 0;
for (i = sc->sc_rxptr;; i = SONIC_NEXTRX(i)) {
ds = &sc->sc_rxsoft[i];
if (sc->sc_32bit) {
SONIC_CDRXSYNC32(sc, i,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
rda32 = &sc->sc_rda32[i];
SONIC_CDRXSYNC32(sc, i, BUS_DMASYNC_PREREAD);
if (rda32->rda_inuse != 0)
break;
status = sonic32toh(sc, rda32->rda_status);
bytecount = sonic32toh(sc, rda32->rda_bytecount);
} else {
SONIC_CDRXSYNC16(sc, i,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
rda16 = &sc->sc_rda16[i];
SONIC_CDRXSYNC16(sc, i, BUS_DMASYNC_PREREAD);
if (rda16->rda_inuse != 0)
break;
status = sonic16toh(sc, rda16->rda_status);
bytecount = sonic16toh(sc, rda16->rda_bytecount);
}
KASSERT(status & RCR_LPKT);
if ((status & RCR_PRX) == 0) {
if (status & RCR_FAER)
printf("%s: Rx frame alignment error\n",
device_xname(sc->sc_dev));
else if (status & RCR_CRCR)
printf("%s: Rx CRC error\n",
device_xname(sc->sc_dev));
if_statinc(ifp, if_ierrors);
SONIC_INIT_RXDESC(sc, i);
continue;
}
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);
len = bytecount - ETHER_CRC_LEN;
#ifndef __NO_STRICT_ALIGNMENT
if (sc->sc_32bit) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto dropit;
if (len > (MHLEN - 2)) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
goto dropit;
}
}
m->m_data += 2;
memcpy(mtod(m, void *), mtod(ds->ds_mbuf, void *),
len);
SONIC_INIT_RXDESC(sc, i);
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
} else
#endif
if (sonic_copy_small != 0 && len <= (MHLEN - 2)) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto dropit;
m->m_data += 2;
memcpy(mtod(m, void *), mtod(ds->ds_mbuf, void *),
len);
SONIC_INIT_RXDESC(sc, i);
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
} else {
m = ds->ds_mbuf;
if (sonic_add_rxbuf(sc, i) != 0) {
dropit:
if_statinc(ifp, if_ierrors);
SONIC_INIT_RXDESC(sc, i);
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize,
BUS_DMASYNC_PREREAD);
continue;
}
}
m_set_rcvif(m, ifp);
m->m_pkthdr.len = m->m_len = len;
if_percpuq_enqueue(ifp->if_percpuq, m);
count++;
}
sc->sc_rxptr = i;
CSR_WRITE(sc, SONIC_RWR, SONIC_CDRRADDR(sc, SONIC_PREVRX(i)));
if (count != 0)
rnd_add_uint32(&sc->sc_rndsource, count);
}
void
sonic_reset(struct sonic_softc *sc)
{
CSR_WRITE(sc, SONIC_CR, CR_STP | CR_RXDIS | CR_HTX);
delay(1000);
CSR_WRITE(sc, SONIC_CR, CR_RST);
delay(1000);
CSR_WRITE(sc, SONIC_IMR, 0);
CSR_WRITE(sc, SONIC_ISR, IMR_ALL);
CSR_WRITE(sc, SONIC_CR, 0);
delay(1000);
}
int
sonic_init(struct ifnet *ifp)
{
struct sonic_softc *sc = ifp->if_softc;
struct sonic_descsoft *ds;
int i, error = 0;
uint16_t reg;
sonic_stop(ifp, 0);
sonic_reset(sc);
reg = sc->sc_dcr;
if (sc->sc_32bit)
reg |= DCR_DW;
CSR_WRITE(sc, SONIC_CR, CR_RST);
CSR_WRITE(sc, SONIC_DCR, reg);
CSR_WRITE(sc, SONIC_DCR2, sc->sc_dcr2);
CSR_WRITE(sc, SONIC_CR, 0);
if (sc->sc_32bit) {
for (i = 0; i < SONIC_NTXDESC; i++) {
memset(&sc->sc_tda32[i], 0, sizeof(struct sonic_tda32));
SONIC_CDTXSYNC32(sc, i,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
} else {
for (i = 0; i < SONIC_NTXDESC; i++) {
memset(&sc->sc_tda16[i], 0, sizeof(struct sonic_tda16));
SONIC_CDTXSYNC16(sc, i,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
}
sc->sc_txpending = 0;
sc->sc_txdirty = 0;
sc->sc_txlast = SONIC_NTXDESC - 1;
for (i = 0; i < SONIC_NRXDESC; i++) {
ds = &sc->sc_rxsoft[i];
if (ds->ds_mbuf == NULL) {
if ((error = sonic_add_rxbuf(sc, i)) != 0) {
printf("%s: unable to allocate or map Rx "
"buffer %d, error = %d\n",
device_xname(sc->sc_dev), i, error);
sonic_rxdrain(sc);
goto out;
}
} else
SONIC_INIT_RXDESC(sc, i);
}
sc->sc_rxptr = 0;
CSR_WRITE(sc, SONIC_UTDAR, (SONIC_CDTXADDR(sc, 0) >> 16) & 0xffff);
CSR_WRITE(sc, SONIC_CTDAR, SONIC_CDTXADDR(sc, 0) & 0xffff);
CSR_WRITE(sc, SONIC_URDAR, (SONIC_CDRXADDR(sc, 0) >> 16) & 0xffff);
CSR_WRITE(sc, SONIC_CRDAR, SONIC_CDRXADDR(sc, 0) & 0xffff);
CSR_WRITE(sc, SONIC_URRAR, (SONIC_CDRRADDR(sc, 0) >> 16) & 0xffff);
CSR_WRITE(sc, SONIC_RSAR, SONIC_CDRRADDR(sc, 0) & 0xffff);
if (sc->sc_32bit)
CSR_WRITE(sc, SONIC_REAR,
(SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1) +
sizeof(struct sonic_rra32)) & 0xffff);
else
CSR_WRITE(sc, SONIC_REAR,
(SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1) +
sizeof(struct sonic_rra16)) & 0xffff);
CSR_WRITE(sc, SONIC_RRR, SONIC_CDRRADDR(sc, 0) & 0xffff);
CSR_WRITE(sc, SONIC_RWR, SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1));
if (sc->sc_32bit)
CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN + 2) / 2);
else
CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN / 2));
CSR_WRITE(sc, SONIC_RSC, 0);
CSR_WRITE(sc, SONIC_CRCETC, 0xffff);
CSR_WRITE(sc, SONIC_FAET, 0xffff);
CSR_WRITE(sc, SONIC_MPT, 0xffff);
sonic_set_filter(sc);
sc->sc_imr = IMR_RFO | IMR_RBA | IMR_RBE | IMR_RDE |
IMR_TXER | IMR_PTX | IMR_PRX;
CSR_WRITE(sc, SONIC_IMR, sc->sc_imr);
CSR_WRITE(sc, SONIC_CR, CR_RXEN | CR_RRRA);
ifp->if_flags |= IFF_RUNNING;
out:
if (error)
printf("%s: interface not running\n", device_xname(sc->sc_dev));
return error;
}
void
sonic_rxdrain(struct sonic_softc *sc)
{
struct sonic_descsoft *ds;
int i;
for (i = 0; i < SONIC_NRXDESC; i++) {
ds = &sc->sc_rxsoft[i];
if (ds->ds_mbuf != NULL) {
bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
m_freem(ds->ds_mbuf);
ds->ds_mbuf = NULL;
}
}
}
void
sonic_stop(struct ifnet *ifp, int disable)
{
struct sonic_softc *sc = ifp->if_softc;
struct sonic_descsoft *ds;
int i;
CSR_WRITE(sc, SONIC_IMR, 0);
CSR_WRITE(sc, SONIC_CR, CR_HTX | CR_RXDIS | CR_STP);
for (i = 0; i < 1000; i++) {
if ((CSR_READ(sc, SONIC_CR) & (CR_TXP | CR_RXEN | CR_ST)) == 0)
break;
delay(2);
}
if ((CSR_READ(sc, SONIC_CR) & (CR_TXP | CR_RXEN | CR_ST)) != 0)
printf("%s: SONIC failed to stop\n", device_xname(sc->sc_dev));
for (i = 0; i < SONIC_NTXDESC; i++) {
ds = &sc->sc_txsoft[i];
if (ds->ds_mbuf != NULL) {
bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
m_freem(ds->ds_mbuf);
ds->ds_mbuf = NULL;
}
}
ifp->if_flags &= ~IFF_RUNNING;
ifp->if_timer = 0;
if (disable)
sonic_rxdrain(sc);
}
int
sonic_add_rxbuf(struct sonic_softc *sc, int idx)
{
struct sonic_descsoft *ds = &sc->sc_rxsoft[idx];
struct mbuf *m;
int error;
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 (ds->ds_mbuf != NULL)
bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
ds->ds_mbuf = m;
error = bus_dmamap_load(sc->sc_dmat, ds->ds_dmamap,
m->m_ext.ext_buf, m->m_ext.ext_size, NULL,
BUS_DMA_READ | BUS_DMA_NOWAIT);
if (error) {
printf("%s: can't load rx DMA map %d, error = %d\n",
device_xname(sc->sc_dev), idx, error);
panic("sonic_add_rxbuf");
}
bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
SONIC_INIT_RXDESC(sc, idx);
return 0;
}
static void
sonic_set_camentry(struct sonic_softc *sc, int entry, const uint8_t *enaddr)
{
if (sc->sc_32bit) {
struct sonic_cda32 *cda = &sc->sc_cda32[entry];
cda->cda_entry = htosonic32(sc, entry);
cda->cda_addr0 = htosonic32(sc, enaddr[0] | (enaddr[1] << 8));
cda->cda_addr1 = htosonic32(sc, enaddr[2] | (enaddr[3] << 8));
cda->cda_addr2 = htosonic32(sc, enaddr[4] | (enaddr[5] << 8));
} else {
struct sonic_cda16 *cda = &sc->sc_cda16[entry];
cda->cda_entry = htosonic16(sc, entry);
cda->cda_addr0 = htosonic16(sc, enaddr[0] | (enaddr[1] << 8));
cda->cda_addr1 = htosonic16(sc, enaddr[2] | (enaddr[3] << 8));
cda->cda_addr2 = htosonic16(sc, enaddr[4] | (enaddr[5] << 8));
}
}
void
sonic_set_filter(struct sonic_softc *sc)
{
struct ethercom *ec = &sc->sc_ethercom;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct ether_multi *enm;
struct ether_multistep step;
int i, entry = 0;
uint16_t camvalid = 0;
uint16_t rcr = 0;
if (ifp->if_flags & IFF_BROADCAST)
rcr |= RCR_BRD;
if (ifp->if_flags & IFF_PROMISC) {
rcr |= RCR_PRO;
goto allmulti;
}
sonic_set_camentry(sc, entry, CLLADDR(ifp->if_sadl));
camvalid |= (1U << entry);
entry++;
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
ETHER_UNLOCK(ec);
goto allmulti;
}
if (entry == SONIC_NCAMENT) {
ETHER_UNLOCK(ec);
goto allmulti;
}
sonic_set_camentry(sc, entry, enm->enm_addrlo);
camvalid |= (1U << entry);
entry++;
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
ifp->if_flags &= ~IFF_ALLMULTI;
goto setit;
allmulti:
camvalid = 0x0001;
entry = 1;
rcr |= RCR_AMC;
setit:
if (sc->sc_32bit) {
if (entry == SONIC_NCAMENT)
sc->sc_cdaenable32 = htosonic32(sc, camvalid);
else
sc->sc_cda32[entry].cda_entry =
htosonic32(sc, camvalid);
} else {
if (entry == SONIC_NCAMENT)
sc->sc_cdaenable16 = htosonic16(sc, camvalid);
else
sc->sc_cda16[entry].cda_entry =
htosonic16(sc, camvalid);
}
SONIC_CDCAMSYNC(sc, BUS_DMASYNC_PREWRITE);
CSR_WRITE(sc, SONIC_CDP, SONIC_CDCAMADDR(sc) & 0xffff);
CSR_WRITE(sc, SONIC_CDC, entry);
CSR_WRITE(sc, SONIC_CR, CR_LCAM);
for (i = 0; i < 10000; i++) {
if ((CSR_READ(sc, SONIC_CR) & CR_LCAM) == 0)
break;
delay(2);
}
if (CSR_READ(sc, SONIC_CR) & CR_LCAM)
printf("%s: CAM load failed\n", device_xname(sc->sc_dev));
SONIC_CDCAMSYNC(sc, BUS_DMASYNC_POSTWRITE);
CSR_WRITE(sc, SONIC_RCR, rcr);
}