#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fec.c,v 1.2 2026/06/30 21:31:31 rkujawa Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/mbuf.h>
#include <sys/bus.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/bpf.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/ofw/openfirm.h>
#include <machine/autoconf.h>
#include <powerpc/mpc5200/obiovar.h>
#include <powerpc/mpc5200/mpc5200reg.h>
#include <powerpc/mpc5200/fecreg.h>
#include <powerpc/mpc5200/fecvar.h>
#include <powerpc/mpc5200/cdmvar.h>
#include <powerpc/mpc5200/bestcommvar.h>
#define FEC_READ(sc, r) bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (r))
#define FEC_WRITE(sc, r, v) bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
#define FEC_RX_TASK 3
#define FEC_TX_TASK 2
#define FEC_RX_INITIATOR 3
#define FEC_TX_INITIATOR 4
static const struct bestcomm_bd_layout fec_rx_layout = BESTCOMM_LAYOUT_FEC_RX;
static const struct bestcomm_bd_layout fec_tx_layout = BESTCOMM_LAYOUT_FEC_TX;
static int fec_match(device_t, cfdata_t, void *);
static void fec_attach(device_t, device_t, void *);
static int fec_miibus_readreg(device_t, int, int, uint16_t *);
static int fec_miibus_writereg(device_t, int, int, uint16_t);
static void fec_miibus_statchg(struct ifnet *);
static int fec_ioctl(struct ifnet *, u_long, void *);
static int fec_init(struct ifnet *);
static void fec_stop(struct ifnet *, int);
static void fec_start(struct ifnet *);
static void fec_tick(void *);
static void fec_reset(struct fec_softc *);
static void fec_mac_setup(struct fec_softc *);
static void fec_set_filter(struct fec_softc *);
static void fec_mib_sync(struct fec_softc *, bool);
static int fec_newbuf(struct fec_softc *, u_int);
static int fec_intr(void *);
static int fec_rxintr(void *);
static int fec_txintr(void *);
CFATTACH_DECL_NEW(fec, sizeof(struct fec_softc),
fec_match, fec_attach, NULL, NULL);
static int
fec_match(device_t parent, cfdata_t cf, void *aux)
{
struct obio_attach_args *oba = aux;
char compat[40];
int len;
if (strcmp(oba->obio_name, "ethernet") == 0)
return 1;
len = OF_getprop(oba->obio_node, "compatible", compat, sizeof(compat));
if (len > 0 &&
(strcmp(compat, "mpc5200-fec") == 0 ||
strcmp(compat, "mpc5200b-fec") == 0))
return 1;
return 0;
}
static void
fec_attach(device_t parent, device_t self, void *aux)
{
struct fec_softc *sc = device_private(self);
struct obio_attach_args *oba = aux;
struct ifnet *ifp = &sc->sc_if;
struct mii_data *mii = &sc->sc_mii;
uint8_t enaddr[ETHER_ADDR_LEN];
bus_size_t size;
int ist;
sc->sc_dev = self;
sc->sc_iot = oba->obio_bst;
sc->sc_dmat = oba->obio_dmat;
sc->sc_pa = oba->obio_addr;
callout_init(&sc->sc_tick_ch, 0);
callout_setfunc(&sc->sc_tick_ch, fec_tick, sc);
size = oba->obio_size != 0 ? oba->obio_size : FEC_REG_SIZE;
if (bus_space_map(sc->sc_iot, oba->obio_addr, size, 0,
&sc->sc_ioh) != 0) {
aprint_error(": can't map registers\n");
return;
}
if (OF_getprop(oba->obio_node, "local-mac-address", enaddr,
sizeof(enaddr)) != sizeof(enaddr) &&
OF_getprop(oba->obio_node, "mac-address", enaddr,
sizeof(enaddr)) != sizeof(enaddr)) {
aprint_error(": no MAC address\n");
return;
}
memcpy(sc->sc_enaddr, enaddr, ETHER_ADDR_LEN);
if (obio_decode_interrupt(oba->obio_node, 0, &sc->sc_irq, &ist))
sc->sc_ih = intr_establish(sc->sc_irq, ist, IPL_NET,
fec_intr, sc);
aprint_normal(": MPC5200 FEC, address %s\n",
ether_sprintf(sc->sc_enaddr));
sc->sc_ipb_freq = mpc5200_cdm_get_ipb_freq();
fec_reset(sc);
mii->mii_ifp = ifp;
mii->mii_readreg = fec_miibus_readreg;
mii->mii_writereg = fec_miibus_writereg;
mii->mii_statchg = fec_miibus_statchg;
sc->sc_ec.ec_mii = mii;
ifmedia_init(&mii->mii_media, 0, ether_mediachange, ether_mediastatus);
mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY,
MIIF_DOPAUSE);
if (LIST_FIRST(&mii->mii_phys) == NULL) {
aprint_error_dev(self, "no PHY found\n");
ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_MANUAL, 0, NULL);
ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_MANUAL);
} else
ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);
sc->sc_ec.ec_capabilities |= ETHERCAP_VLAN_MTU;
strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_init = fec_init;
ifp->if_stop = fec_stop;
ifp->if_start = fec_start;
ifp->if_ioctl = fec_ioctl;
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, sc->sc_enaddr);
}
static void
fec_reset(struct fec_softc *sc)
{
uint32_t div;
int i;
FEC_WRITE(sc, FEC_ECNTRL, FEC_ECNTRL_RESET);
for (i = 0; i < 1000; i++) {
if ((FEC_READ(sc, FEC_ECNTRL) & FEC_ECNTRL_RESET) == 0)
break;
DELAY(10);
}
FEC_WRITE(sc, FEC_IMASK, 0);
FEC_WRITE(sc, FEC_IEVENT, 0xffffffff);
div = howmany(sc->sc_ipb_freq, 2 * FEC_MII_MAX_CLK);
if (div > 0x3f)
div = 0x3f;
FEC_WRITE(sc, FEC_MII_SPEED, FEC_MII_SPEED_DIV(div));
}
static int
fec_mii_wait(struct fec_softc *sc)
{
int i;
for (i = 0; i < FEC_PHY_TIMEOUT; i++) {
if (FEC_READ(sc, FEC_IEVENT) & FEC_INT_MII) {
FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII);
return 0;
}
DELAY(10);
}
return ETIMEDOUT;
}
static int
fec_miibus_readreg(device_t self, int phy, int reg, uint16_t *val)
{
struct fec_softc *sc = device_private(self);
FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII);
FEC_WRITE(sc, FEC_MII_DATA, FEC_MII_READ(phy, reg));
if (fec_mii_wait(sc) != 0)
return ETIMEDOUT;
*val = FEC_READ(sc, FEC_MII_DATA) & FEC_MII_DATA_MASK;
return 0;
}
static int
fec_miibus_writereg(device_t self, int phy, int reg, uint16_t val)
{
struct fec_softc *sc = device_private(self);
FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII);
FEC_WRITE(sc, FEC_MII_DATA, FEC_MII_WRITE(phy, reg, val));
return fec_mii_wait(sc);
}
static void
fec_miibus_statchg(struct ifnet *ifp)
{
struct fec_softc *sc = ifp->if_softc;
uint32_t xcr, rcr;
xcr = FEC_READ(sc, FEC_X_CNTRL);
if (sc->sc_mii.mii_media_active & IFM_FDX)
xcr |= FEC_X_CNTRL_FDEN;
else
xcr &= ~FEC_X_CNTRL_FDEN;
FEC_WRITE(sc, FEC_X_CNTRL, xcr);
rcr = FEC_READ(sc, FEC_R_CNTRL);
if (sc->sc_mii.mii_media_active & IFM_ETH_RXPAUSE)
rcr |= FEC_R_CNTRL_FCE;
else
rcr &= ~FEC_R_CNTRL_FCE;
FEC_WRITE(sc, FEC_R_CNTRL, rcr);
}
static void
fec_tick(void *arg)
{
struct fec_softc *sc = arg;
int s;
s = splnet();
mii_tick(&sc->sc_mii);
if (sc->sc_if.if_flags & IFF_RUNNING)
fec_mib_sync(sc, false);
splx(s);
if ((sc->sc_if.if_flags & IFF_RUNNING) &&
(FEC_READ(sc, FEC_ECNTRL) & FEC_ECNTRL_ETHER_EN) == 0) {
if_statinc(&sc->sc_if, if_ierrors);
aprint_error_dev(sc->sc_dev,
"ETHER_EN cleared, resetting\n");
s = splnet();
(void)fec_init(&sc->sc_if);
splx(s);
return;
}
callout_schedule(&sc->sc_tick_ch, hz);
}
static void
fec_mac_setup(struct fec_softc *sc)
{
FEC_WRITE(sc, FEC_PADDR1,
(sc->sc_enaddr[0] << 24) | (sc->sc_enaddr[1] << 16) |
(sc->sc_enaddr[2] << 8) | sc->sc_enaddr[3]);
FEC_WRITE(sc, FEC_PADDR2,
(sc->sc_enaddr[4] << 24) | (sc->sc_enaddr[5] << 16) | 0x8808);
FEC_WRITE(sc, FEC_IADDR1, 0);
FEC_WRITE(sc, FEC_IADDR2, 0);
FEC_WRITE(sc, FEC_R_CNTRL,
FEC_R_CNTRL_MAX_FL(ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN) |
FEC_R_CNTRL_MII_MODE);
FEC_WRITE(sc, FEC_X_CNTRL, FEC_X_CNTRL_FDEN);
FEC_WRITE(sc, FEC_RESET_CNTRL, FEC_RESET_CNTRL_FIFO_EN);
FEC_WRITE(sc, FEC_RFIFO_CONTROL, FEC_FIFO_CTRL_INIT);
FEC_WRITE(sc, FEC_TFIFO_CONTROL, FEC_FIFO_CTRL_INIT);
FEC_WRITE(sc, FEC_TFIFO_ALARM, 0x80);
}
static void
fec_set_filter(struct fec_softc *sc)
{
struct ethercom *ec = &sc->sc_ec;
struct ifnet *ifp = &sc->sc_if;
struct ether_multi *enm;
struct ether_multistep step;
uint32_t gaddr[2] = { 0, 0 };
uint32_t rcr;
u_int h;
rcr = FEC_READ(sc, FEC_R_CNTRL) & ~FEC_R_CNTRL_PROM;
if (ifp->if_flags & IFF_PROMISC) {
rcr |= FEC_R_CNTRL_PROM;
gaddr[0] = gaddr[1] = 0xffffffff;
goto done;
}
ETHER_LOCK(ec);
ec->ec_flags &= ~ETHER_F_ALLMULTI;
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
ETHER_ADDR_LEN) != 0) {
ec->ec_flags |= ETHER_F_ALLMULTI;
gaddr[0] = gaddr[1] = 0xffffffff;
break;
}
h = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN) >> 26;
gaddr[h >> 5] |= 1U << (h & 0x1f);
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
done:
FEC_WRITE(sc, FEC_GADDR1, gaddr[1]);
FEC_WRITE(sc, FEC_GADDR2, gaddr[0]);
FEC_WRITE(sc, FEC_R_CNTRL, rcr);
}
static const struct fec_mibcnt {
bus_size_t reg;
if_stat_t stat;
} fec_mibtab[] = {
{ FEC_RMON_T_COL, if_collisions },
{ FEC_IEEE_T_LCOL, if_oerrors },
{ FEC_IEEE_T_EXCOL, if_oerrors },
{ FEC_IEEE_T_MACERR, if_oerrors },
{ FEC_IEEE_R_DROP, if_iqdrops },
{ FEC_IEEE_R_CRC, if_ierrors },
{ FEC_IEEE_R_ALIGN, if_ierrors },
{ FEC_IEEE_R_MACERR, if_iqdrops },
};
__CTASSERT(__arraycount(fec_mibtab) == FEC_NMIB);
static void
fec_mib_sync(struct fec_softc *sc, bool prime)
{
struct ifnet *ifp = &sc->sc_if;
net_stat_ref_t nsr;
u_int i;
if (prime) {
for (i = 0; i < FEC_NMIB; i++)
sc->sc_mib_prev[i] = FEC_READ(sc, fec_mibtab[i].reg);
return;
}
nsr = IF_STAT_GETREF(ifp);
for (i = 0; i < FEC_NMIB; i++) {
uint32_t cur = FEC_READ(sc, fec_mibtab[i].reg);
uint32_t delta = cur - sc->sc_mib_prev[i];
if (delta != 0)
if_statadd_ref(ifp, nsr, fec_mibtab[i].stat, delta);
sc->sc_mib_prev[i] = cur;
}
IF_STAT_PUTREF(ifp);
}
static int
fec_newbuf(struct fec_softc *sc, u_int 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;
}
m->m_len = m->m_pkthdr.len = MCLBYTES;
error = bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_rxmap[idx], m,
BUS_DMA_NOWAIT | BUS_DMA_READ);
if (error) {
m_freem(m);
return error;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0, MCLBYTES,
BUS_DMASYNC_PREREAD);
sc->sc_rxmbuf[idx] = m;
bestcomm_bd_post(&sc->sc_rxring, idx,
sc->sc_rxmap[idx]->dm_segs[0].ds_addr, MCLBYTES, 0);
return 0;
}
static int
fec_init(struct ifnet *ifp)
{
struct fec_softc *sc = ifp->if_softc;
u_int i;
int error;
bool link_up;
link_up = (sc->sc_mii.mii_media_status & IFM_ACTIVE) != 0;
fec_stop(ifp, 0);
if (!bestcomm_available()) {
aprint_error_ifnet(ifp, "BestComm SDMA not available\n");
return ENXIO;
}
fec_reset(sc);
fec_mac_setup(sc);
fec_set_filter(sc);
FEC_WRITE(sc, FEC_MIB_CONTROL, 0);
fec_mib_sync(sc, true);
if (bestcomm_bd_setup(&sc->sc_rxring, FEC_RX_TASK, &fec_rx_layout,
sc->sc_pa + FEC_RFIFO_DATA, FEC_NRXDESC, MCLBYTES, 4,
FEC_RX_INITIATOR, 6) != 0 ||
bestcomm_bd_setup(&sc->sc_txring, FEC_TX_TASK, &fec_tx_layout,
sc->sc_pa + FEC_TFIFO_DATA, FEC_NTXDESC, MCLBYTES, 4,
FEC_TX_INITIATOR, 5) != 0) {
aprint_error_ifnet(ifp, "cannot set up SDMA rings\n");
error = ENOMEM;
goto fail;
}
for (i = 0; i < FEC_NRXDESC; i++) {
if (sc->sc_rxmap[i] == NULL &&
bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0,
BUS_DMA_NOWAIT, &sc->sc_rxmap[i]) != 0) {
error = ENOMEM;
goto fail;
}
if ((error = fec_newbuf(sc, i)) != 0)
goto fail;
}
for (i = 0; i < FEC_NTXDESC; i++) {
if (sc->sc_txmap[i] == NULL &&
bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0,
BUS_DMA_NOWAIT, &sc->sc_txmap[i]) != 0) {
error = ENOMEM;
goto fail;
}
}
sc->sc_rxptr = 0;
sc->sc_txnext = sc->sc_txdirty = sc->sc_txbusy = 0;
sc->sc_rxih = intr_establish(bestcomm_task_irq(FEC_RX_TASK), IST_LEVEL,
IPL_NET, fec_rxintr, sc);
sc->sc_txih = intr_establish(bestcomm_task_irq(FEC_TX_TASK), IST_LEVEL,
IPL_NET, fec_txintr, sc);
mii_mediachg(&sc->sc_mii);
if (!link_up) {
for (i = 0; i < 500; i++) {
mii_pollstat(&sc->sc_mii);
if (sc->sc_mii.mii_media_status & IFM_ACTIVE)
break;
DELAY(10000);
}
}
FEC_WRITE(sc, FEC_ECNTRL, FEC_ECNTRL_ETHER_EN);
bestcomm_task_start(FEC_RX_TASK);
bestcomm_task_start(FEC_TX_TASK);
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
callout_schedule(&sc->sc_tick_ch, hz);
return 0;
fail:
fec_stop(ifp, 1);
return error;
}
static void
fec_stop(struct ifnet *ifp, int disable)
{
struct fec_softc *sc = ifp->if_softc;
u_int i;
callout_stop(&sc->sc_tick_ch);
bestcomm_bd_teardown(&sc->sc_rxring);
bestcomm_bd_teardown(&sc->sc_txring);
FEC_WRITE(sc, FEC_ECNTRL, 0);
if (sc->sc_rxih != NULL) {
intr_disestablish(sc->sc_rxih);
sc->sc_rxih = NULL;
}
if (sc->sc_txih != NULL) {
intr_disestablish(sc->sc_txih);
sc->sc_txih = NULL;
}
mii_down(&sc->sc_mii);
for (i = 0; i < FEC_NRXDESC; i++) {
if (sc->sc_rxmbuf[i] != NULL) {
bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[i]);
m_freem(sc->sc_rxmbuf[i]);
sc->sc_rxmbuf[i] = NULL;
}
}
for (i = 0; i < FEC_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;
}
}
ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
}
static void
fec_start(struct ifnet *ifp)
{
struct fec_softc *sc = ifp->if_softc;
struct mbuf *m, *mn;
u_int idx;
int error, posted = 0;
if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
while (sc->sc_txbusy < FEC_NTXDESC) {
IFQ_POLL(&ifp->if_snd, m);
if (m == NULL)
break;
idx = sc->sc_txnext;
error = bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_txmap[idx], m,
BUS_DMA_NOWAIT | BUS_DMA_WRITE);
if (error == EFBIG) {
mn = m_defrag(m, M_DONTWAIT);
if (mn != NULL) {
m = mn;
error = bus_dmamap_load_mbuf(sc->sc_dmat,
sc->sc_txmap[idx], m,
BUS_DMA_NOWAIT | BUS_DMA_WRITE);
}
}
IFQ_DEQUEUE(&ifp->if_snd, m);
if (error) {
m_freem(m);
if_statinc(ifp, if_oerrors);
continue;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[idx], 0,
m->m_pkthdr.len, BUS_DMASYNC_PREWRITE);
bpf_mtap(ifp, m, BPF_D_OUT);
sc->sc_txmbuf[idx] = m;
bestcomm_bd_post(&sc->sc_txring, idx,
sc->sc_txmap[idx]->dm_segs[0].ds_addr, m->m_pkthdr.len,
BESTCOMM_BD_FLAG_LAST | BESTCOMM_BD_FLAG_INT);
sc->sc_txnext = (idx + 1) % FEC_NTXDESC;
sc->sc_txbusy++;
posted = 1;
}
if (posted)
bestcomm_task_start(FEC_TX_TASK);
if (sc->sc_txbusy == FEC_NTXDESC)
ifp->if_flags |= IFF_OACTIVE;
}
static int
fec_rxintr(void *arg)
{
struct fec_softc *sc = arg;
struct ifnet *ifp = &sc->sc_if;
struct mbuf *m;
uint32_t st;
u_int idx;
int len, handled = 0;
for (;;) {
idx = sc->sc_rxptr;
st = bestcomm_bd_status(&sc->sc_rxring, idx);
if (st & BESTCOMM_BD_READY)
break;
handled = 1;
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0, MCLBYTES,
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[idx]);
m = sc->sc_rxmbuf[idx];
sc->sc_rxmbuf[idx] = NULL;
len = st & BESTCOMM_BD_LEN_MASK;
if (len > ETHER_CRC_LEN)
len -= ETHER_CRC_LEN;
if (fec_newbuf(sc, idx) != 0) {
sc->sc_rxmbuf[idx] = m;
(void)bus_dmamap_load_mbuf(sc->sc_dmat,
sc->sc_rxmap[idx], m, BUS_DMA_NOWAIT | BUS_DMA_READ);
bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0,
MCLBYTES, BUS_DMASYNC_PREREAD);
bestcomm_bd_post(&sc->sc_rxring, idx,
sc->sc_rxmap[idx]->dm_segs[0].ds_addr, MCLBYTES, 0);
if_statinc(ifp, if_ierrors);
} else {
m_set_rcvif(m, ifp);
m->m_pkthdr.len = m->m_len = len;
if_percpuq_enqueue(ifp->if_percpuq, m);
}
sc->sc_rxptr = (idx + 1) % FEC_NRXDESC;
}
return handled;
}
static int
fec_txintr(void *arg)
{
struct fec_softc *sc = arg;
struct ifnet *ifp = &sc->sc_if;
uint32_t st;
u_int idx;
int handled = 0;
while (sc->sc_txbusy > 0) {
idx = sc->sc_txdirty;
st = bestcomm_bd_status(&sc->sc_txring, idx);
if (st & BESTCOMM_BD_READY)
break;
handled = 1;
bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[idx], 0,
sc->sc_txmbuf[idx]->m_pkthdr.len, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[idx]);
m_freem(sc->sc_txmbuf[idx]);
sc->sc_txmbuf[idx] = NULL;
if_statinc(ifp, if_opackets);
sc->sc_txdirty = (idx + 1) % FEC_NTXDESC;
sc->sc_txbusy--;
}
if (handled) {
ifp->if_flags &= ~IFF_OACTIVE;
if_schedule_deferred_start(ifp);
}
return handled;
}
static int
fec_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct fec_softc *sc = ifp->if_softc;
int s, error;
s = splnet();
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
fec_set_filter(sc);
error = 0;
}
splx(s);
return error;
}
static int
fec_intr(void *arg)
{
struct fec_softc *sc = arg;
uint32_t ievent;
ievent = FEC_READ(sc, FEC_IEVENT);
FEC_WRITE(sc, FEC_IEVENT, ievent);
return ievent != 0;
}