/*-
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Device driver for the National Semiconductor DP83932
* Systems-Oriented Network Interface Controller (SONIC).
*/
/*
* sonic_attach:
*
* Attach a SONIC interface to the system.
*/
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;
/*
* Allocate the control data structures, and create and load the
* DMA map for it.
*/
if (sc->sc_32bit)
cdatasize = sizeof(struct sonic_control_data32);
else
cdatasize = sizeof(struct sonic_control_data16);
/*
* Make sure the interface is shutdown during reboot.
*/
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;
/*
* Free any resources we've allocated during the failed attach
* attempt. Do this in reverse order and fall through.
*/
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;
}
/*
* sonic_shutdown:
*
* Make sure the interface is stopped at reboot.
*/
bool
sonic_shutdown(device_t self, int howto)
{
struct sonic_softc *sc = device_private(self);
if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING)
return;
/*
* Remember the previous txpending and the current "last txdesc
* used" index.
*/
opending = sc->sc_txpending;
olasttx = sc->sc_txlast;
/*
* Loop through the send queue, setting up transmit descriptors
* until we drain the queue, or use up all available transmit
* descriptors. Leave one at the end for sanity's sake.
*/
while (sc->sc_txpending < (SONIC_NTXDESC - 1)) {
/*
* Grab a packet off the queue.
*/
IFQ_POLL(&ifp->if_snd, m0);
if (m0 == NULL)
break;
m = NULL;
/*
* Get the next available transmit descriptor.
*/
nexttx = SONIC_NEXTTX(sc->sc_txlast);
ds = &sc->sc_txsoft[nexttx];
dmamap = ds->ds_dmamap;
/*
* Load the DMA map. If this fails, the packet either
* didn't fit in the allotted number of frags, or we were
* short on resources. In this case, we'll copy and try
* again.
*/
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;
}
/*
* WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET.
*/
/* Advance the Tx pointer. */
sc->sc_txpending++;
sc->sc_txlast = nexttx;
/*
* Pass the packet to any BPF listeners.
*/
bpf_mtap(ifp, m0, BPF_D_OUT);
}
if (sc->sc_txpending != opending) {
/*
* We enqueued packets. If the transmitter was idle,
* reset the txdirty pointer.
*/
if (opending == 0)
sc->sc_txdirty = SONIC_NEXTTX(olasttx);
/*
* Stop the SONIC on the last packet we've set up,
* and clear end-of-list on the descriptor previous
* to our new chain.
*
* NOTE: our `seg' variable should still be valid!
*/
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);
}
/* Start the transmitter. */
CSR_WRITE(sc, SONIC_CR, CR_TXP);
/* Set a watchdog timer in case the chip flakes out. */
ifp->if_timer = 5;
}
}
/*
* Make absolutely sure this is the only packet
* in this receive buffer. Our entire Rx buffer
* management scheme depends on this, and if the
* SONIC didn't follow our rule, it means we've
* misconfigured it.
*/
KASSERT(status & RCR_LPKT);
/*
* Make sure the packet arrived OK. If an error occurred,
* update stats and reset the descriptor. The buffer will
* be reused the next time the descriptor comes up in the
* ring.
*/
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;
}
/*
* The SONIC includes the CRC with every packet.
*/
len = bytecount - ETHER_CRC_LEN;
/*
* Ok, if the chip is in 32-bit mode, then receive
* buffers must be aligned to 32-bit boundaries,
* which means the payload is misaligned. In this
* case, we must allocate a new mbuf, and copy the
* packet into it, scooted forward 2 bytes to ensure
* proper alignment.
*
* Note, in 16-bit mode, we can configure the SONIC
* to do what we want, and we have.
*/
#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;
/*
* Note that we use a cluster for incoming frames,
* so the buffer is virtually contiguous.
*/
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 /* ! __NO_STRICT_ALIGNMENT */
/*
* If the packet is small enough to fit in a single
* header mbuf, allocate one and copy the data into
* it. This greatly reduces memory consumption when
* we receive lots of small packets.
*/
if (sonic_copy_small != 0 && len <= (MHLEN - 2)) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto dropit;
m->m_data += 2;
/*
* Note that we use a cluster for incoming frames,
* so the buffer is virtually contiguous.
*/
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;
}
}
/*
* sonic_init: [ifnet interface function]
*
* Initialize the interface. Must be called at splnet().
*/
int
sonic_init(struct ifnet *ifp)
{
struct sonic_softc *sc = ifp->if_softc;
struct sonic_descsoft *ds;
int i, error = 0;
uint16_t reg;
/*
* Cancel any pending I/O.
*/
sonic_stop(ifp, 0);
/*
* Reset the SONIC to a known state.
*/
sonic_reset(sc);
/*
* Bring the SONIC into reset state, and program the DCR.
*
* Note: We don't bother optimizing the transmit and receive
* thresholds, here. TFT/RFT values should be set in MD attachments.
*/
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);
/*
* Initialize the transmit descriptors.
*/
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;
/*
* Initialize the receive descriptor ring.
*/
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);
/*
* XXX Should attempt to run with fewer receive
* XXX buffers instead of just failing.
*/
sonic_rxdrain(sc);
goto out;
}
} else
SONIC_INIT_RXDESC(sc, i);
}
sc->sc_rxptr = 0;
/* Give the transmit ring to the SONIC. */
CSR_WRITE(sc, SONIC_UTDAR, (SONIC_CDTXADDR(sc, 0) >> 16) & 0xffff);
CSR_WRITE(sc, SONIC_CTDAR, SONIC_CDTXADDR(sc, 0) & 0xffff);
/* Give the receive descriptor ring to the SONIC. */
CSR_WRITE(sc, SONIC_URDAR, (SONIC_CDRXADDR(sc, 0) >> 16) & 0xffff);
CSR_WRITE(sc, SONIC_CRDAR, SONIC_CDRXADDR(sc, 0) & 0xffff);
/*
* Set the End-Of-Buffer counter such that only one packet
* will be placed into each buffer we provide. Note we are
* following the recommendation of section 3.4.4 of the manual
* here, and have "lengthened" the receive buffers accordingly.
*/
if (sc->sc_32bit)
CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN + 2) / 2);
else
CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN / 2));
/* Reset the receive sequence counter. */
CSR_WRITE(sc, SONIC_RSC, 0);
/*
* Start the receive process in motion. Note, we don't
* start the transmit process until we actually try to
* transmit packets.
*/
CSR_WRITE(sc, SONIC_CR, CR_RXEN | CR_RRRA);
/* Put our station address in the first CAM slot. */
sonic_set_camentry(sc, entry, CLLADDR(ifp->if_sadl));
camvalid |= (1U << entry);
entry++;
/* Add the multicast addresses to the CAM. */
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
/*
* We must listen to a range of multicast addresses.
* The only way to do this on the SONIC is to enable
* reception of all multicast packets.
*/
ETHER_UNLOCK(ec);
goto allmulti;
}
if (entry == SONIC_NCAMENT) {
/*
* Out of CAM slots. Have to enable reception
* of all multicast addresses.
*/
ETHER_UNLOCK(ec);
goto allmulti;
}