/* $NetBSD: if_gfe.c,v 1.61 2024/07/05 04:31:51 rin Exp $ */
/*
* Copyright (c) 2002 Allegro Networks, Inc., Wasabi Systems, Inc.
* All rights reserved.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Allegro Networks, Inc., and Wasabi Systems, Inc.
* 4. The name of Allegro Networks, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* 5. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY ALLEGRO NETWORKS, INC. AND
* WASABI SYSTEMS, INC. ``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 EITHER ALLEGRO NETWORKS, INC. OR WASABI SYSTEMS, INC.
* 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.
*/
if (sc->sc_pcr & ETH_EPCR_EN) {
int tries = 1000;
/*
* Abort transmitter and receiver and wait for them to quiese
*/
GE_WRITE(sc, ETH_ESDCMR, ETH_ESDCMR_AR | ETH_ESDCMR_AT);
do {
delay(100);
if (tries-- <= 0) {
aprint_error_dev(self, "Abort TX/RX failed\n");
break;
}
} while (GE_READ(sc, ETH_ESDCMR) &
(ETH_ESDCMR_AR | ETH_ESDCMR_AT));
}
int
gfe_dmamem_alloc(struct gfe_softc *sc, struct gfe_dmamem *gdm, int maxsegs,
size_t size, int flags)
{
int error = 0;
GE_FUNC_ENTER(sc, "gfe_dmamem_alloc");
for (;;) {
IF_POLL(&ifp->if_snd, m);
if (m == NULL) {
ifp->if_flags &= ~IFF_OACTIVE;
GE_FUNC_EXIT(sc, "");
return;
}
/*
* No space in the pending queue? try later.
*/
if (IF_QFULL(&sc->sc_txq[GE_TXPRIO_HI].txq_pendq))
break;
IF_DEQUEUE(&ifp->if_snd, m);
/*
* Try to enqueue a mbuf to the device. If that fails, we
* can always try to map the next mbuf.
*/
IF_ENQUEUE(&sc->sc_txq[GE_TXPRIO_HI].txq_pendq, m);
GE_DPRINTF(sc, (">"));
#ifndef GE_NOTX
(void) gfe_tx_enqueue(sc, GE_TXPRIO_HI);
#endif
}
/*
* Attempt to queue the mbuf for send failed.
*/
ifp->if_flags |= IFF_OACTIVE;
GE_FUNC_EXIT(sc, "%%");
}
GE_RXDPOSTSYNC(sc, rxq, rxq->rxq_fi);
cmdsts = gt32toh(rxd->ed_cmdsts);
GE_DPRINTF(sc, (":%d=%#x", rxq->rxq_fi, cmdsts));
rxq->rxq_cmdsts = cmdsts;
/*
* Sometimes the GE "forgets" to reset the ownership bit.
* But if the length has been rewritten, the packet is ours
* so pretend the O bit is set.
*/
buflen = gt32toh(rxd->ed_lencnt) & 0xffff;
if ((cmdsts & RX_CMD_O) && buflen == 0) {
GE_RXDPRESYNC(sc, rxq, rxq->rxq_fi);
break;
}
/*
* If this is not a single buffer packet with no errors
* or for some reason it's bigger than our frame size,
* ignore it and go to the next packet.
*/
if ((cmdsts & (RX_CMD_F | RX_CMD_L | RX_STS_ES)) !=
(RX_CMD_F | RX_CMD_L) ||
(buflen > sc->sc_max_frame_length)) {
GE_DPRINTF(sc, ("!"));
--rxq->rxq_active;
if_statinc(ifp, if_ipackets);
if_statinc(ifp, if_ierrors);
goto give_it_back;
}
/* CRC is included with the packet; trim it off. */
buflen -= ETHER_CRC_LEN;
/*
* Anything in the pending queue to enqueue? if not, punt. Likewise
* if the txq is not yet created.
* otherwise grab its dmamap.
*/
if (txq == NULL || (m = txq->txq_pendq.ifq_head) == NULL) {
GE_FUNC_EXIT(sc, "-");
return 0;
}
/*
* Have we [over]consumed our limit of descriptors?
* Do we have enough free descriptors?
*/
if (GE_TXDESC_MAX == txq->txq_nactive + 2) {
volatile struct gt_eth_desc * const txd2 = &txq->txq_descs[txq->txq_fi];
uint32_t cmdsts;
size_t pktlen;
GE_TXDPOSTSYNC(sc, txq, txq->txq_fi);
cmdsts = gt32toh(txd2->ed_cmdsts);
if (cmdsts & TX_CMD_O) {
int nextin;
/*
* Sometime the Discovery forgets to update the
* last descriptor. See if we own the descriptor
* after it (since we know we've turned that to
* the discovery and if we owned it, the Discovery
* gave it back). If we do, we know the Discovery
* gave back this one but forgot to mark it as ours.
*/
nextin = txq->txq_fi + 1;
if (nextin == GE_TXDESC_MAX)
nextin = 0;
GE_TXDPOSTSYNC(sc, txq, nextin);
if (gt32toh(txq->txq_descs[nextin].ed_cmdsts) & TX_CMD_O) {
GE_TXDPRESYNC(sc, txq, txq->txq_fi);
GE_TXDPRESYNC(sc, txq, nextin);
GE_FUNC_EXIT(sc, "@");
return 0;
}
#ifdef DEBUG
printf("%s: txenqueue: transmitter resynced at %d\n",
device_xname(sc->sc_dev), txq->txq_fi);
#endif
}
if (++txq->txq_fi == GE_TXDESC_MAX)
txq->txq_fi = 0;
txq->txq_inptr = gt32toh(txd2->ed_bufptr) - txq->txq_buf_busaddr;
pktlen = (gt32toh(txd2->ed_lencnt) >> 16) & 0xffff;
txq->txq_inptr += roundup(pktlen, dcache_line_size);
txq->txq_nactive--;
/*
* If this packet would wrap around the end of the buffer, reset back
* to the beginning.
*/
if (txq->txq_outptr + buflen > GE_TXBUF_SIZE) {
txq->txq_ei_gapcount += GE_TXBUF_SIZE - txq->txq_outptr;
txq->txq_outptr = 0;
}
/*
* Make sure the output packet doesn't run over the beginning of
* what we've already given the GT.
*/
if (txq->txq_nactive > 0 && txq->txq_outptr <= txq->txq_inptr &&
txq->txq_outptr + buflen > txq->txq_inptr) {
intrmask |= txq->txq_intrbits &
(ETH_IR_TxBufferHigh | ETH_IR_TxBufferLow);
if (sc->sc_intrmask != intrmask) {
sc->sc_intrmask = intrmask;
GE_WRITE(sc, ETH_EIMR, sc->sc_intrmask);
}
GE_FUNC_EXIT(sc, "#");
return 0;
}
/*
* The end-of-list descriptor we put on last time is the starting point
* for this packet. The GT is supposed to terminate list processing on
* a NULL nxtptr but that currently is broken so a CPU-owned descriptor
* must terminate the list.
*/
intrmask = sc->sc_intrmask;
/*
* Request a buffer interrupt every 2/3 of the way thru the transmit
* buffer.
*/
txq->txq_ei_gapcount += buflen;
if (txq->txq_ei_gapcount > 2 * GE_TXBUF_SIZE / 3) {
txd->ed_cmdsts = htogt32(TX_CMD_FIRST |TX_CMD_LAST |TX_CMD_EI);
txq->txq_ei_gapcount = 0;
} else {
txd->ed_cmdsts = htogt32(TX_CMD_FIRST | TX_CMD_LAST);
}
#if 0
GE_DPRINTF(sc, ("([%d]->%08lx.%08lx.%08lx.%08lx)", txq->txq_lo,
((unsigned long *)txd)[0], ((unsigned long *)txd)[1],
((unsigned long *)txd)[2], ((unsigned long *)txd)[3]));
#endif
GE_TXDPRESYNC(sc, txq, txq->txq_lo);
txq->txq_outptr += buflen;
/*
* Tell the SDMA engine to "Fetch!"
*/
GE_WRITE(sc, ETH_ESDCMR,
txq->txq_esdcmrbits & (ETH_ESDCMR_TXDH | ETH_ESDCMR_TXDL));
GE_DPRINTF(sc, ("(%d)", txq->txq_lo));
/*
* Update the last out appropriately.
*/
txq->txq_nactive++;
if (++txq->txq_lo == GE_TXDESC_MAX)
txq->txq_lo = 0;
/*
* Move mbuf from the pending queue to the snd queue.
*/
IF_DEQUEUE(&txq->txq_pendq, m);
bpf_mtap(ifp, m, BPF_D_OUT);
m_freem(m);
ifp->if_flags &= ~IFF_OACTIVE;
/*
* Since we have put an item into the packet queue, we now want
* an interrupt when the transmit queue finishes processing the
* list. But only update the mask if needs changing.
*/
intrmask |= txq->txq_intrbits & (ETH_IR_TxEndHigh | ETH_IR_TxEndLow);
if (sc->sc_intrmask != intrmask) {
sc->sc_intrmask = intrmask;
GE_WRITE(sc, ETH_EIMR, sc->sc_intrmask);
}
if (ifp->if_timer == 0)
ifp->if_timer = 5;
GE_FUNC_EXIT(sc, "*");
return 1;
}
if (txq == NULL) {
GE_FUNC_EXIT(sc, "");
return intrmask;
}
while (txq->txq_nactive > 0) {
const int dcache_line_size = curcpu()->ci_ci.dcache_line_size;
volatile struct gt_eth_desc *txd = &txq->txq_descs[txq->txq_fi];
uint32_t cmdsts;
size_t pktlen;
GE_TXDPOSTSYNC(sc, txq, txq->txq_fi);
if ((cmdsts = gt32toh(txd->ed_cmdsts)) & TX_CMD_O) {
int nextin;
if (txq->txq_nactive == 1) {
GE_TXDPRESYNC(sc, txq, txq->txq_fi);
GE_FUNC_EXIT(sc, "");
return intrmask;
}
/*
* Sometimes the Discovery forgets to update the
* ownership bit in the descriptor. See if we own the
* descriptor after it (since we know we've turned
* that to the Discovery and if we own it now then the
* Discovery gave it back). If we do, we know the
* Discovery gave back this one but forgot to mark it
* as ours.
*/
nextin = txq->txq_fi + 1;
if (nextin == GE_TXDESC_MAX)
nextin = 0;
GE_TXDPOSTSYNC(sc, txq, nextin);
if (gt32toh(txq->txq_descs[nextin].ed_cmdsts) & TX_CMD_O) {
GE_TXDPRESYNC(sc, txq, txq->txq_fi);
GE_TXDPRESYNC(sc, txq, nextin);
GE_FUNC_EXIT(sc, "");
return intrmask;
}
#ifdef DEBUG
printf("%s: txdone: transmitter resynced at %d\n",
device_xname(sc->sc_dev), txq->txq_fi);
#endif
}
#if 0
GE_DPRINTF(sc, ("([%d]<-%08lx.%08lx.%08lx.%08lx)",
txq->txq_lo,
((unsigned long *)txd)[0], ((unsigned long *)txd)[1],
((unsigned long *)txd)[2], ((unsigned long *)txd)[3]));
#endif
GE_DPRINTF(sc, ("(%d)", txq->txq_fi));
if (++txq->txq_fi == GE_TXDESC_MAX)
txq->txq_fi = 0;
txq->txq_inptr = gt32toh(txd->ed_bufptr) - txq->txq_buf_busaddr;
pktlen = (gt32toh(txd->ed_lencnt) >> 16) & 0xffff;
bus_dmamap_sync(sc->sc_dmat, txq->txq_buf_mem.gdm_map,
txq->txq_inptr, pktlen, BUS_DMASYNC_POSTWRITE);
txq->txq_inptr += roundup(pktlen, dcache_line_size);
/*
* If we are restarting, there may be packets in the pending queue
* waiting to be enqueued. Try enqueuing packets from both priority
* queues until the pending queue is empty or there no room for them
* on the device.
*/
while (gfe_tx_enqueue(sc, txprio))
continue;
GE_DPRINTF(sc, ("%s=", ether_sprintf(eaddr)));
/*
* hashResult is the 15 bits Hash entry address.
* ethernetADD is a 48 bit number, which is derived from the Ethernet
* MAC address, by nibble swapping in every byte (i.e MAC address
* of 0x123456789abc translates to ethernetADD of 0x21436587a9cb).
*/
if ((sc->sc_pcr & ETH_EPCR_HM) == 0) {
/*
* hashResult[14:0] = hashFunc0(ethernetADD[47:0])
*
* hashFunc0 calculates the hashResult in the following manner:
* hashResult[ 8:0] = ethernetADD[14:8,1,0]
* XOR ethernetADD[23:15] XOR ethernetADD[32:24]
*/
result = (add0 & 3) | ((add0 >> 6) & ~3);
result ^= (add0 >> 15) ^ (add1 >> 0);
result &= 0x1ff;
/*
* hashResult[14:9] = ethernetADD[7:2]
*/
result |= (add0 & ~3) << 7; /* excess bits will be masked */
GE_DPRINTF(sc, ("0(%#x)", result & 0x7fff));
} else {
#define TRIBITFLIP 073516240 /* yes its in octal */
/*
* hashResult[14:0] = hashFunc1(ethernetADD[47:0])
*
* hashFunc1 calculates the hashResult in the following manner:
* hashResult[08:00] = ethernetADD[06:14]
* XOR ethernetADD[15:23] XOR ethernetADD[24:32]
*/
w0 = ((add0 >> 6) ^ (add0 >> 15) ^ (add1)) & 0x1ff;
/*
* Now bitswap those 9 bits
*/
result = 0;
result |= ((TRIBITFLIP >> (((w0 >> 0) & 7) * 3)) & 7) << 6;
result |= ((TRIBITFLIP >> (((w0 >> 3) & 7) * 3)) & 7) << 3;
result |= ((TRIBITFLIP >> (((w0 >> 6) & 7) * 3)) & 7) << 0;
/*
* Assume we are going to insert so create the hash entry we
* are going to insert. We also use it to match entries we
* will be removing.
*/
he = ((uint64_t) eaddr[5] << 43) |
((uint64_t) eaddr[4] << 35) |
((uint64_t) eaddr[3] << 27) |
((uint64_t) eaddr[2] << 19) |
((uint64_t) eaddr[1] << 11) |
((uint64_t) eaddr[0] << 3) |
HSH_PRIO_INS(prio) | HSH_V | HSH_R;
/*
* The GT will search upto 12 entries for a hit, so we must mimic that.
*/
hash &= sc->sc_hashmask / sizeof(he);
for (limit = HSH_LIMIT; limit > 0 ; --limit) {
/*
* Does the GT wrap at the end, stop at the, or overrun the
* end? Assume it wraps for now. Stash a copy of the
* current hash entry.
*/
uint64_t *he_p = &sc->sc_hashtable[hash];
uint64_t thishe = *he_p;
/*
* If the hash entry isn't valid, that break the chain. And
* this entry a good candidate for reuse.
*/
if ((thishe & HSH_V) == 0) {
maybe_he_p = he_p;
break;
}
/*
* If the hash entry has the same address we are looking for
* then ... if we are removing and the skip bit is set, its
* already been removed. if are adding and the skip bit is
* clear, then its already added. In either return EBUSY
* indicating the op has already been done. Otherwise flip
* the skip bit and return 0.
*/
if (((he ^ thishe) & HSH_ADDR_MASK) == 0) {
if (((op == GE_HASH_REMOVE) && (thishe & HSH_S)) ||
((op == GE_HASH_ADD) && (thishe & HSH_S) == 0))
return EBUSY;
*he_p = thishe ^ HSH_S;
bus_dmamap_sync(sc->sc_dmat, sc->sc_hash_mem.gdm_map,
hash * sizeof(he), sizeof(he),
BUS_DMASYNC_PREWRITE);
GE_FUNC_EXIT(sc, "^");
return 0;
}
/*
* If we haven't found a slot for the entry and this entry
* is currently being skipped, return this entry.
*/
if (maybe_he_p == NULL && (thishe & HSH_S)) {
maybe_he_p = he_p;
maybe_hash = hash;
}
GE_FUNC_ENTER(sc, "hash_multichg");
/*
* Is this a wildcard entry? If so and its being removed, recompute.
*/
if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) != 0) {
if (cmd == SIOCDELMULTI) {
GE_FUNC_EXIT(sc, "");
return ENETRESET;
}
if (error == ENOENT) {
aprint_error_dev(sc->sc_dev,
"multichg: failed to remove %s: not in table\n",
ether_sprintf(enm->enm_addrlo));
GE_FUNC_EXIT(sc, "");
return 0;
}