/* $NetBSD: if_le_ebus.c,v 1.26 2024/07/05 04:31:49 rin Exp $ */
/*-
* Copyright (c) 2010 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code was written by Alessandro Forin and Neil Pittman
* at Microsoft Research and contributed to The NetBSD Foundation
* by Microsoft Corporation.
*
* 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.
*/
struct enic_softc {
device_t sc_dev; /* base device glue */
struct ethercom sc_ethercom; /* Ethernet common part */
struct ifmedia sc_media; /* our supported media */
struct _Enic *sc_regs; /* hw registers */
int sc_havecarrier; /* carrier status */
void *sc_sh; /* shutdownhook cookie */
int inited;
int sc_no_rd;
int sc_n_recv;
int sc_recv_h;
/* BUGBUG really should be malloc-ed */
#define SC_MAX_N_RECV 64
struct bufmap sc_recv[SC_MAX_N_RECV];
int sc_no_td;
int sc_n_xmit;
int sc_xmit_h;
/* BUGBUG really should be malloc-ed */
#define SC_MAX_N_XMIT 16
struct bufmap sc_xmit[SC_MAX_N_XMIT];
bool sc_txbusy;
#if DEBUG
int xhit;
int xmiss;
int tfull;
int tfull2;
int brh;
int rf;
int bxh;
/* Get the MAC and the depth of the FIFOs */
if (!enic_gethwinfo(sc)) {
printf(" <cannot get hw info> DISABLED.\n");
/*
* NB: caveat maintainer: make sure what we
* did NOT do below does not hurt the system
*/
return;
}
sc->sc_dev = self;
sc->sc_no_td = 0;
sc->sc_havecarrier = 1; /* BUGBUG */
sc->sc_recv_h = 0;
sc->sc_xmit_h = 0;
/* uhmm do I need to do this? */
memset(sc->sc_recv, 0, sizeof sc->sc_recv);
memset(sc->sc_xmit, 0, sizeof sc->sc_xmit);
/*
* Beware: does not work while the nic is running
*/
static int enic_gethwinfo(struct enic_softc *sc)
{
uint8_t buffer[8]; /* 64bits max */
PENIC_INFO hw = (PENIC_INFO)buffer;
paddr_t phys = kvtophys((vaddr_t)&buffer[0]), phys2;
int i;
/*
* First thing first, get the MAC address
*/
memset(buffer, 0, sizeof buffer);
buffer[0] = ENIC_CMD_GET_ADDRESS;
buffer[3] = ENIC_CMD_GET_ADDRESS; /* bswap bug */
sc->sc_regs->SizeAndFlags = (sizeof buffer) | ES_F_CMD;
sc->sc_regs->BufferAddressHi32 = 0;
sc->sc_regs->BufferAddressLo32 = phys; /* go! */
for (i = 0; i < 100; i++) {
DELAY(100);
if ((sc->sc_regs->Control & EC_OF_EMPTY) == 0)
break;
}
if (i == 100)
return 0;
/*
* NB: only "ifconfig down" says suspend=1 (then "up" calls init)
* Could simply set RXDIS in this case
*/
sc->inited = 2;
sc->sc_regs->Control = EC_RESET;
sc->sc_no_rd = 0; /* they are gone */
sc->sc_no_td = 0; /* they are gone */
}
/* Operational reload? */
if (m != NULL) {
/* But is the hw ready for it */
if (sc->sc_regs->Control & EC_IF_FULL)
goto no_room;
/* Yes, find a spot. Include empty q case. */
for (i = sc->sc_recv_h; i < sc->sc_n_recv; i++)
if (sc->sc_recv[i].mbuf == NULL)
goto found;
for (i = 0; i < sc->sc_recv_h; i++)
if (sc->sc_recv[i].mbuf == NULL)
goto found;
/* no spot, drop it (sigh) */
no_room:
#if DEBUG
sc->rf++;
#endif
m_freem(m);
return;
found:
phys = kvtophys((vaddr_t)m->m_data);
sc->sc_recv[i].mbuf = m;
sc->sc_recv[i].phys = phys;
rpostone(phys);
sc->sc_no_rd++;
return;
}
/* Repost after reset? */
if (sc->inited) {
/* order doesnt matter, might as well keep it clean */
int j = 0;
sc->sc_recv_h = 0;
for (i = 0; i < sc->sc_n_recv; i++)
if ((m = sc->sc_recv[i].mbuf) != NULL) {
phys = sc->sc_recv[i].phys;
sc->sc_recv[i].mbuf = NULL;
sc->sc_recv[i].phys = ~0;
sc->sc_recv[j].mbuf = m;
sc->sc_recv[j].phys = phys;
#if DEBUG
if (sc->sc_regs->Control & EC_IF_FULL)
printf("?uhu? postrecv full? %d\n",
sc->sc_no_rd);
#endif
sc->sc_no_rd++;
rpostone(phys);
j++;
}
/* Any holes left? */
sc->inited = 1;
if (j >= sc->sc_n_recv)
return; /* no, we are done */
/* continue on with the loop below */
i = j; m = NULL;
goto fillem;
}
/* Initial refill, we can wait */
waitmode = M_WAIT;
sc->sc_recv_h = 0;
memset(sc->sc_recv, 0, sizeof(sc->sc_recv[0]) * sc->sc_n_recv);
i = 0;
fillem:
for (; i < sc->sc_n_recv; i++) {
MGETHDR(m, waitmode, MT_DATA);
if (m == 0)
break;
m_set_rcvif(m, &sc->sc_ethercom.ec_if);
m->m_pkthdr.len = 0;
MCLGET(m, waitmode);
if ((m->m_flags & M_EXT) == 0)
break;
/*
* pull out all completed buffers
*/
while ((isr & EC_OF_EMPTY) == 0) {
/* beware, order matters */
saf = sc->sc_regs->SizeAndFlags;
hi = sc->sc_regs->BufferAddressHi32; /* BUGBUG 64bit */
lo = sc->sc_regs->BufferAddressLo32; /* this pops the fifo */
__USE(hi);
fl = saf & (ES_F_MASK &~ ES_F_DONE);
if (fl == ES_F_RECV)
enic_rint(sc, saf, lo);
else if (fl == ES_F_XMIT)
enic_tint(sc, saf, lo);
else {
/*
* we do not currently expect or care for
* command completions?
*/
if (fl != ES_F_CMD)
printf("%s: invalid saf=x%x (lo=%x)\n",
device_xname(sc->sc_dev), saf, lo);
}
isr = sc->sc_regs->Control;
}
rnd_add_uint32(&sc->rnd_source, isr);
return 1;
}
void
enic_rint(struct enic_softc *sc, uint32_t saf, paddr_t phys)
{
struct mbuf *m;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
int len = saf & ES_S_MASK, i;
/* Find what buffer it is. Should be the first. */
for (i = sc->sc_recv_h; i < sc->sc_n_recv; i++)
if (sc->sc_recv[i].phys == phys)
goto found;
for (i = 0; i < sc->sc_recv_h; i++)
if (sc->sc_recv[i].phys == phys)
goto found;
/* BUGBUG should there be a per-buffer error bit in SAF? */
/* Find what buffer it is. Should be the first. */
for (i = sc->sc_xmit_h; i < sc->sc_n_xmit; i++)
if (sc->sc_xmit[i].phys == phys)
goto found;
for (i = 0; i < sc->sc_xmit_h; i++)
if (sc->sc_xmit[i].phys == phys)
goto found;
/*
* Setup output on interface.
* Get another datagram to send off of the interface queue, and map it to the
* interface before starting the output.
* Called only at splnet or interrupt level.
*/
void
enic_start(struct ifnet *ifp)
{
struct enic_softc *sc = ifp->if_softc;
struct mbuf *m;
int len, ix, s;
paddr_t phys;
/* Anything to do? */
IFQ_POLL(&ifp->if_snd, m);
if (m == NULL)
break;
/* find a spot, if any */
for (; ix < sc->sc_n_xmit; ix++)
if (sc->sc_xmit[ix].mbuf == NULL)
goto found;
for (ix = 0; ix < sc->sc_xmit_h; ix++)
if (sc->sc_xmit[ix].mbuf == NULL)
goto found;
/* oh well */
sc->sc_txbusy = true;
#if DEBUG
sc->tfull++;
#endif
break;
found:
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == NULL)
break;
/*
* If BPF is listening on this interface, let it see the packet
* before we commit it to the wire.
*/
bpf_mtap(ifp, m, BPF_D_OUT);
/*
* Copy the mbuf chain into a contiguous transmit buffer.
*/
len = enic_put(sc, &m);
if (len == 0)
break; /* sanity */
if (len > (ETHERMTU + sizeof(struct ether_header))) {
printf("enic? tlen %d > %d\n",
len, ETHERMTU + sizeof(struct ether_header));
len = ETHERMTU + sizeof(struct ether_header);
}
ifp->if_timer = 5;
/*
* Remember and post the buffer
*/
phys = kvtophys((vaddr_t)m->m_data);
sc->sc_xmit[ix].mbuf = m;
sc->sc_xmit[ix].phys = phys;