#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mb86960.c,v 1.100 2026/05/23 06:08:21 andvar Exp $");
#include "opt_inet.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/rndsource.h>
#include <sys/bus.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_inarp.h>
#endif
#include <dev/ic/mb86960reg.h>
#include <dev/ic/mb86960var.h>
#ifndef __BUS_SPACE_HAS_STREAM_METHODS
#define bus_space_write_stream_2 bus_space_write_2
#define bus_space_write_multi_stream_2 bus_space_write_multi_2
#define bus_space_read_multi_stream_2 bus_space_read_multi_2
#endif
void mb86960_init(struct mb86960_softc *);
int mb86960_ioctl(struct ifnet *, u_long, void *);
void mb86960_start(struct ifnet *);
void mb86960_reset(struct mb86960_softc *);
void mb86960_watchdog(struct ifnet *);
int mb86960_get_packet(struct mb86960_softc *, u_int);
void mb86960_stop(struct mb86960_softc *);
void mb86960_tint(struct mb86960_softc *, uint8_t);
void mb86960_rint(struct mb86960_softc *, uint8_t);
static inline
void mb86960_xmit(struct mb86960_softc *);
void mb86960_write_mbufs(struct mb86960_softc *, struct mbuf *);
static inline
void mb86960_droppacket(struct mb86960_softc *);
void mb86960_getmcaf(struct ethercom *, uint8_t *);
void mb86960_setmode(struct mb86960_softc *);
void mb86960_loadmar(struct mb86960_softc *);
int mb86960_mediachange(struct ifnet *);
void mb86960_mediastatus(struct ifnet *, struct ifmediareq *);
#if FE_DEBUG >= 1
void mb86960_dump(int, struct mb86960_softc *);
#endif
void
mb86960_attach(struct mb86960_softc *sc, uint8_t *myea)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL;
sc->proto_dlcr5 = 0;
sc->proto_dlcr7 = FE_D7_BYTSWP_LH;
if ((sc->sc_flags & FE_FLAGS_MB86960) != 0)
sc->proto_dlcr7 |= FE_D7_ED_TEST;
sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO;
sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB |
FE_D6_BBW_BYTE | FE_D6_SRAM_100ns;
if (sc->sc_flags & FE_FLAGS_SBW_BYTE)
sc->proto_dlcr6 |= FE_D6_SBW_BYTE;
if (sc->sc_flags & FE_FLAGS_SRAM_150ns)
sc->proto_dlcr6 &= ~FE_D6_SRAM_100ns;
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_DISABLE);
delay(200);
#ifdef DIAGNOSTIC
if (myea == NULL) {
aprint_error_dev(sc->sc_dev,
"ethernet address shouldn't be NULL\n");
panic("NULL ethernet address");
}
#endif
memcpy(sc->sc_enaddr, myea, sizeof(sc->sc_enaddr));
bus_space_write_1(bst, bsh, FE_DLCR2, 0);
bus_space_write_1(bst, bsh, FE_DLCR3, 0);
}
void
mb86960_config(struct mb86960_softc *sc, int *media, int nmedia, int defmedia)
{
cfdata_t cf = device_cfdata(sc->sc_dev);
struct ifnet *ifp = &sc->sc_ec.ec_if;
int i;
mb86960_stop(sc);
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_start = mb86960_start;
ifp->if_ioctl = mb86960_ioctl;
ifp->if_watchdog = mb86960_watchdog;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
IFQ_SET_READY(&ifp->if_snd);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: mb86960_config()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
#if FE_SINGLE_TRANSMISSION
sc->proto_dlcr6 &= ~FE_D6_TXBSIZ;
sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB;
#endif
if ((cf->cf_flags & FE_FLAGS_OVERRIDE_DLCR6) != 0)
sc->proto_dlcr6 = cf->cf_flags & FE_FLAGS_DLCR6_VALUE;
switch (sc->proto_dlcr6 & FE_D6_TXBSIZ) {
case FE_D6_TXBSIZ_2x2KB:
sc->txb_size = 2048;
break;
case FE_D6_TXBSIZ_2x4KB:
sc->txb_size = 4096;
break;
case FE_D6_TXBSIZ_2x8KB:
sc->txb_size = 8192;
break;
default:
#if FE_DEBUG >= 2
log(LOG_WARNING, "%s: strange TXBSIZ config; fixing\n",
device_xname(sc->sc_dev));
#endif
sc->proto_dlcr6 &= ~FE_D6_TXBSIZ;
sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB;
sc->txb_size = 2048;
break;
}
sc->sc_ec.ec_ifmedia = &sc->sc_media;
ifmedia_init(&sc->sc_media, 0, mb86960_mediachange,
mb86960_mediastatus);
if (media != NULL) {
for (i = 0; i < nmedia; i++)
ifmedia_add(&sc->sc_media, media[i], 0, NULL);
ifmedia_set(&sc->sc_media, defmedia);
} else {
ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_MANUAL, 0, NULL);
ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_MANUAL);
}
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, sc->sc_enaddr);
rnd_attach_source(&sc->rnd_source, device_xname(sc->sc_dev),
RND_TYPE_NET, RND_FLAG_DEFAULT);
aprint_normal_dev(sc->sc_dev, "Ethernet address %s\n",
ether_sprintf(sc->sc_enaddr));
#if FE_DEBUG >= 3
{
int buf, txb, bbw, sbw, ram;
buf = txb = bbw = sbw = ram = -1;
switch (sc->proto_dlcr6 & FE_D6_BUFSIZ) {
case FE_D6_BUFSIZ_8KB:
buf = 8;
break;
case FE_D6_BUFSIZ_16KB:
buf = 16;
break;
case FE_D6_BUFSIZ_32KB:
buf = 32;
break;
case FE_D6_BUFSIZ_64KB:
buf = 64;
break;
}
switch (sc->proto_dlcr6 & FE_D6_TXBSIZ) {
case FE_D6_TXBSIZ_2x2KB:
txb = 2;
break;
case FE_D6_TXBSIZ_2x4KB:
txb = 4;
break;
case FE_D6_TXBSIZ_2x8KB:
txb = 8;
break;
}
switch (sc->proto_dlcr6 & FE_D6_BBW) {
case FE_D6_BBW_BYTE:
bbw = 8;
break;
case FE_D6_BBW_WORD:
bbw = 16;
break;
}
switch (sc->proto_dlcr6 & FE_D6_SBW) {
case FE_D6_SBW_BYTE:
sbw = 8;
break;
case FE_D6_SBW_WORD:
sbw = 16;
break;
}
switch (sc->proto_dlcr6 & FE_D6_SRAM) {
case FE_D6_SRAM_100ns:
ram = 100;
break;
case FE_D6_SRAM_150ns:
ram = 150;
break;
}
aprint_debug_dev(sc->sc_dev,
"SRAM %dKB %dbit %dns, TXB %dKBx2, %dbit I/O\n",
buf, bbw, ram, txb, sbw);
}
#endif
sc->sc_stat |= FE_STAT_ATTACHED;
}
int
mb86960_mediachange(struct ifnet *ifp)
{
struct mb86960_softc *sc = ifp->if_softc;
if (sc->sc_mediachange)
return (*sc->sc_mediachange)(sc);
return 0;
}
void
mb86960_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct mb86960_softc *sc = ifp->if_softc;
if ((sc->sc_stat & FE_STAT_ENABLED) == 0) {
ifmr->ifm_active = IFM_ETHER | IFM_NONE;
ifmr->ifm_status = 0;
return;
}
if (sc->sc_mediastatus)
(*sc->sc_mediastatus)(sc, ifmr);
}
void
mb86960_reset(struct mb86960_softc *sc)
{
int s;
s = splnet();
mb86960_stop(sc);
mb86960_init(sc);
splx(s);
}
void
mb86960_stop(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: top of mb86960_stop()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
bus_space_write_1(bst, bsh, FE_DLCR2, 0x00);
bus_space_write_1(bst, bsh, FE_DLCR3, 0x00);
delay(200);
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_DISABLE);
delay(200);
bus_space_write_1(bst, bsh, FE_DLCR0, 0xFF);
bus_space_write_1(bst, bsh, FE_DLCR1, 0xFF);
delay(200);
bus_space_write_1(bst, bsh, FE_DLCR7,
sc->proto_dlcr7 | FE_D7_POWER_DOWN);
delay(200);
sc->filter_change = 0;
if (sc->stop_card)
(*sc->stop_card)(sc);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: end of mb86960_stop()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
}
void
mb86960_watchdog(struct ifnet *ifp)
{
struct mb86960_softc *sc = ifp->if_softc;
log(LOG_ERR, "%s: device timeout\n", device_xname(sc->sc_dev));
#if FE_DEBUG >= 3
mb86960_dump(LOG_INFO, sc);
#endif
if_statadd(ifp, if_oerrors, sc->txb_sched + sc->txb_count);
mb86960_reset(sc);
}
static inline void
mb86960_droppacket(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
bus_space_write_1(bst, bsh, FE_BMPR14, FE_B14_FILTER | FE_B14_SKIP);
}
void
mb86960_init(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifnet *ifp = &sc->sc_ec.ec_if;
int i;
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: top of mb86960_init()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
sc->txb_free = sc->txb_size;
sc->txb_count = 0;
sc->txb_sched = 0;
if (sc->init_card)
(*sc->init_card)(sc);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: after init hook\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_DISABLE);
delay(200);
bus_space_write_1(bst, bsh, FE_DLCR7,
sc->proto_dlcr7 | FE_D7_RBS_DLCR | FE_D7_POWER_UP);
delay(200);
bus_space_write_region_1(bst, bsh, FE_DLCR8,
sc->sc_enaddr, ETHER_ADDR_LEN);
bus_space_write_1(bst, bsh, FE_DLCR7,
sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP);
bus_space_write_1(bst, bsh, FE_DLCR0, 0xFF);
bus_space_write_1(bst, bsh, FE_DLCR1, 0xFF);
bus_space_write_1(bst, bsh, FE_DLCR2, 0x00);
bus_space_write_1(bst, bsh, FE_DLCR3, 0x00);
bus_space_write_1(bst, bsh, FE_DLCR4, sc->proto_dlcr4);
bus_space_write_1(bst, bsh, FE_DLCR5, sc->proto_dlcr5);
bus_space_write_1(bst, bsh, FE_BMPR10, 0x00);
bus_space_write_1(bst, bsh, FE_BMPR11, FE_B11_CTRL_SKIP);
bus_space_write_1(bst, bsh, FE_BMPR12, 0x00);
bus_space_write_1(bst, bsh, FE_BMPR13, sc->proto_bmpr13);
bus_space_write_1(bst, bsh, FE_BMPR14, FE_B14_FILTER);
bus_space_write_1(bst, bsh, FE_BMPR15, 0x00);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: just before enabling DLC\n",
device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
bus_space_write_1(bst, bsh, FE_DLCR2, FE_TMASK);
bus_space_write_1(bst, bsh, FE_DLCR3, FE_RMASK);
delay(200);
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_ENABLE);
delay(200);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: just after enabling DLC\n",
device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
for (i = 0; i < FE_MAX_RECV_COUNT; i++) {
if (bus_space_read_1(bst, bsh, FE_DLCR5) & FE_D5_BUFEMP)
break;
mb86960_droppacket(sc);
}
#if FE_DEBUG >= 1
if (i >= FE_MAX_RECV_COUNT)
log(LOG_ERR, "%s: cannot empty receive buffer\n",
device_xname(sc->sc_dev));
#endif
#if FE_DEBUG >= 3
if (i < FE_MAX_RECV_COUNT)
log(LOG_INFO, "%s: receive buffer emptied (%d)\n",
device_xname(sc->sc_dev), i);
#endif
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: after ERB loop\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
bus_space_write_1(bst, bsh, FE_DLCR0, 0xFF);
bus_space_write_1(bst, bsh, FE_DLCR1, 0xFF);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: after FIXME\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
ifp->if_flags |= IFF_RUNNING;
mb86960_setmode(sc);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: after setmode\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
mb86960_start(ifp);
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: end of mb86960_init()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
}
static inline void
mb86960_xmit(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
sc->sc_ec.ec_if.if_timer = 1 + sc->txb_count;
sc->txb_sched = sc->txb_count;
sc->txb_count = 0;
sc->txb_free = sc->txb_size;
#if FE_DELAYED_PADDING
sc->txb_padding = 0;
#endif
bus_space_write_1(bst, bsh, FE_BMPR10, sc->txb_sched | FE_B10_START);
}
void
mb86960_start(struct ifnet *ifp)
{
struct mb86960_softc *sc = ifp->if_softc;
struct mbuf *m;
#if FE_DEBUG >= 1
if ((sc->txb_count == 0) != (sc->txb_free == sc->txb_size)) {
log(LOG_ERR, "%s: inconsistent txb variables (%d, %d)\n",
device_xname(sc->sc_dev), sc->txb_count, sc->txb_free);
sc->txb_count = 0;
}
#endif
#if FE_DEBUG >= 1
if ((sc->txb_count > 0) && (sc->txb_sched == 0)) {
log(LOG_ERR, "%s: transmitter idle with %d buffered packets\n",
device_xname(sc->sc_dev), sc->txb_count);
mb86960_xmit(sc);
}
#endif
if (sc->filter_change) {
goto indicate_active;
}
for (;;) {
if (sc->txb_free <
(ETHER_MAX_LEN - ETHER_CRC_LEN) + FE_TXLEN_SIZE) {
goto indicate_active;
}
#if FE_SINGLE_TRANSMISSION
if (sc->txb_count > 0) {
goto indicate_active;
}
#endif
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == 0) {
goto indicate_inactive;
}
bpf_mtap(ifp, m, BPF_D_OUT);
mb86960_write_mbufs(sc, m);
m_freem(m);
if (sc->txb_sched == 0)
mb86960_xmit(sc);
}
indicate_inactive:
ifp->if_flags &= ~IFF_OACTIVE;
return;
indicate_active:
ifp->if_flags |= IFF_OACTIVE;
return;
}
void
mb86960_tint(struct mb86960_softc *sc, uint8_t tstat)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifnet *ifp = &sc->sc_ec.ec_if;
int left;
int col;
if (tstat & FE_D0_COLL16) {
left = bus_space_read_1(bst, bsh, FE_BMPR10);
#if FE_DEBUG >= 2
log(LOG_WARNING, "%s: excessive collision (%d/%d)\n",
device_xname(sc->sc_dev), left, sc->txb_sched);
#endif
#if FE_DEBUG >= 3
mb86960_dump(LOG_INFO, sc);
#endif
net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
if_statadd_ref(ifp, nsr, if_collisions, 16);
if_statinc_ref(ifp, nsr, if_oerrors);
if_statadd_ref(ifp, nsr, if_opackets, sc->txb_sched - left);
IF_STAT_PUTREF(ifp);
bus_space_write_1(bst, bsh, FE_DLCR0, FE_D0_COLLID);
bus_space_write_1(bst, bsh, FE_BMPR11,
FE_B11_CTRL_SKIP | FE_B11_MODE1);
sc->txb_sched = left - 1;
}
if (tstat & FE_D0_TXDONE) {
if (bus_space_read_1(bst, bsh, FE_DLCR0) & FE_D0_COLLID) {
bus_space_write_1(bst, bsh, FE_DLCR0, FE_D0_COLLID);
col = bus_space_read_1(bst, bsh, FE_DLCR4) & FE_D4_COL;
if (col == 0) {
col = 1;
} else
col >>= FE_D4_COL_SHIFT;
if_statadd(ifp, if_collisions, col);
#if FE_DEBUG >= 4
log(LOG_WARNING, "%s: %d collision%s (%d)\n",
device_xname(sc->sc_dev), col, col == 1 ? "" : "s",
sc->txb_sched);
#endif
}
if_statadd(ifp, if_opackets, sc->txb_sched);
sc->txb_sched = 0;
}
if (sc->txb_sched == 0) {
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
if (sc->txb_count > 0)
mb86960_xmit(sc);
}
}
void
mb86960_rint(struct mb86960_softc *sc, uint8_t rstat)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifnet *ifp = &sc->sc_ec.ec_if;
u_int status, len;
int i;
if (rstat & (FE_D1_OVRFLO | FE_D1_CRCERR | FE_D1_ALGERR |
FE_D1_SRTPKT)) {
#if FE_DEBUG >= 3
char sbuf[sizeof(FE_D1_ERRBITS) + 64];
snprintb(sbuf, sizeof(sbuf), FE_D1_ERRBITS, rstat);
log(LOG_WARNING, "%s: receive error: %s\n",
device_xname(sc->sc_dev), sbuf);
#endif
if_statinc(ifp, if_ierrors);
}
for (i = 0; i < FE_MAX_RECV_COUNT; i++) {
if (bus_space_read_1(bst, bsh, FE_DLCR5) & FE_D5_BUFEMP)
break;
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
status = bus_space_read_1(bst, bsh, FE_BMPR8);
(void)bus_space_read_1(bst, bsh, FE_BMPR8);
} else
status = bus_space_read_2(bst, bsh, FE_BMPR8);
#if FE_DEBUG >= 4
log(LOG_INFO, "%s: receive status = %02x\n",
device_xname(sc->sc_dev), status);
#endif
if ((status & FE_RXSTAT_GOODPKT) == 0) {
if ((ifp->if_flags & IFF_PROMISC) == 0) {
if_statinc(ifp, if_ierrors);
mb86960_droppacket(sc);
continue;
}
}
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
len = bus_space_read_1(bst, bsh, FE_BMPR8);
len |= bus_space_read_1(bst, bsh, FE_BMPR8) << 8;
} else
len = bus_space_read_2(bst, bsh, FE_BMPR8);
if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN) ||
len < ETHER_HDR_LEN) {
#if FE_DEBUG >= 2
log(LOG_WARNING,
"%s: received a %s packet? (%u bytes)\n",
device_xname(sc->sc_dev),
len < ETHER_HDR_LEN ? "partial" : "big", len);
#endif
if_statinc(ifp, if_ierrors);
mb86960_droppacket(sc);
continue;
}
#if FE_DEBUG >= 2
if (len < (ETHER_MIN_LEN - ETHER_CRC_LEN)) {
log(LOG_WARNING,
"%s: received a short packet? (%u bytes)\n",
device_xname(sc->sc_dev), len);
}
#endif
if (mb86960_get_packet(sc, len) == 0) {
#if FE_DEBUG >= 2
log(LOG_WARNING,
"%s: out of mbufs; dropping packet (%u bytes)\n",
device_xname(sc->sc_dev), len);
#endif
if_statinc(ifp, if_ierrors);
mb86960_droppacket(sc);
return;
}
}
}
int
mb86960_intr(void *arg)
{
struct mb86960_softc *sc = arg;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifnet *ifp = &sc->sc_ec.ec_if;
uint8_t tstat, rstat;
if ((sc->sc_stat & FE_STAT_ENABLED) == 0 ||
!device_is_active(sc->sc_dev))
return 0;
#if FE_DEBUG >= 4
log(LOG_INFO, "%s: mb86960_intr()\n", device_xname(sc->sc_dev));
mb86960_dump(LOG_INFO, sc);
#endif
tstat = bus_space_read_1(bst, bsh, FE_DLCR0) & FE_TMASK;
rstat = bus_space_read_1(bst, bsh, FE_DLCR1) & FE_RMASK;
if (tstat == 0 && rstat == 0)
return 0;
for (;;) {
bus_space_write_1(bst, bsh, FE_DLCR0, tstat);
bus_space_write_1(bst, bsh, FE_DLCR1, rstat);
if (tstat != 0)
mb86960_tint(sc, tstat);
if (rstat != 0)
mb86960_rint(sc, rstat);
if (sc->filter_change &&
sc->txb_count == 0 && sc->txb_sched == 0) {
mb86960_loadmar(sc);
ifp->if_flags &= ~IFF_OACTIVE;
}
if ((ifp->if_flags & IFF_OACTIVE) == 0)
if_schedule_deferred_start(ifp);
if (rstat != 0 || tstat != 0)
rnd_add_uint32(&sc->rnd_source, rstat + tstat);
tstat = bus_space_read_1(bst, bsh, FE_DLCR0) & FE_TMASK;
rstat = bus_space_read_1(bst, bsh, FE_DLCR1) & FE_RMASK;
if (tstat == 0 && rstat == 0)
return 1;
}
}
int
mb86960_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct mb86960_softc *sc = ifp->if_softc;
struct ifaddr *ifa = (struct ifaddr *)data;
int s, error = 0;
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: ioctl(%lx)\n", device_xname(sc->sc_dev), cmd);
#endif
s = splnet();
switch (cmd) {
case SIOCINITIFADDR:
if ((error = mb86960_enable(sc)) != 0)
break;
ifp->if_flags |= IFF_UP;
mb86960_init(sc);
switch (ifa->ifa_addr->sa_family) {
#ifdef INET
case AF_INET:
arp_ifinit(ifp, ifa);
break;
#endif
default:
break;
}
break;
case SIOCSIFFLAGS:
if ((error = ifioctl_common(ifp, cmd, data)) != 0)
break;
switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) {
case IFF_RUNNING:
mb86960_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
mb86960_disable(sc);
break;
case IFF_UP:
if ((error = mb86960_enable(sc)) != 0)
break;
mb86960_init(sc);
break;
case IFF_UP | IFF_RUNNING:
mb86960_setmode(sc);
break;
case 0:
break;
}
#if FE_DEBUG >= 1
if (ifp->if_flags & IFF_DEBUG) {
log(LOG_INFO, "%s: SIOCSIFFLAGS(DEBUG)\n",
device_xname(sc->sc_dev));
mb86960_dump(LOG_DEBUG, sc);
}
#endif
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
if ((sc->sc_stat & FE_STAT_ENABLED) == 0) {
error = EIO;
break;
}
if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
mb86960_setmode(sc);
error = 0;
}
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
splx(s);
return error;
}
int
mb86960_get_packet(struct mb86960_softc *sc, u_int len)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifnet *ifp = &sc->sc_ec.ec_if;
struct mbuf *m;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == 0)
return 0;
m_set_rcvif(m, ifp);
m->m_pkthdr.len = len;
#define EROUND ((sizeof(struct ether_header) + 3) & ~3)
#define EOFF (EROUND - sizeof(struct ether_header))
if (len > MHLEN - EOFF) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
return 0;
}
}
m->m_data += EOFF;
m->m_len = len;
if (sc->sc_flags & FE_FLAGS_SBW_BYTE)
bus_space_read_multi_1(bst, bsh, FE_BMPR8,
mtod(m, uint8_t *), len);
else
bus_space_read_multi_stream_2(bst, bsh, FE_BMPR8,
mtod(m, uint16_t *), (len + 1) >> 1);
if_percpuq_enqueue(ifp->if_percpuq, m);
return 1;
}
void
mb86960_write_mbufs(struct mb86960_softc *sc, struct mbuf *m)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
int totlen, len;
#if FE_DEBUG >= 2
struct mbuf *mp;
#endif
#if FE_DELAYED_PADDING
if (sc->txb_padding > 0) {
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
for (len = sc->txb_padding; len > 0; len--)
bus_space_write_1(bst, bsh, FE_BMPR8, 0);
} else {
for (len = sc->txb_padding >> 1; len > 0; len--)
bus_space_write_2(bst, bsh, FE_BMPR8, 0);
}
sc->txb_padding = 0;
}
#endif
if ((m->m_flags & M_PKTHDR) == 0)
panic("mb86960_write_mbufs: no header mbuf");
#if FE_DEBUG >= 2
for (totlen = 0, mp = m; mp != 0; mp = mp->m_next)
totlen += mp->m_len;
if (totlen != m->m_pkthdr.len)
log(LOG_WARNING, "%s: packet length mismatch? (%d/%d)\n",
device_xname(sc->sc_dev), totlen, m->m_pkthdr.len);
#else
totlen = m->m_pkthdr.len;
#endif
#if FE_DEBUG >= 1
if (totlen > (ETHER_MAX_LEN - ETHER_CRC_LEN) ||
totlen < ETHER_HDR_LEN) {
log(LOG_ERR, "%s: got a %s packet (%u bytes) to send\n",
device_xname(sc->sc_dev),
totlen < ETHER_HDR_LEN ? "partial" : "big", totlen);
if_statinc(&sc->sc_ec.ec_if, if_oerrors);
return;
}
#endif
len = uimax(totlen, (ETHER_MIN_LEN - ETHER_CRC_LEN));
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
bus_space_write_1(bst, bsh, FE_BMPR8, len);
bus_space_write_1(bst, bsh, FE_BMPR8, len >> 8);
} else {
bus_space_write_2(bst, bsh, FE_BMPR8, len);
totlen = (totlen + 1) & ~1;
}
sc->txb_free -= FE_TXLEN_SIZE +
uimax(totlen, (ETHER_MIN_LEN - ETHER_CRC_LEN));
sc->txb_count++;
#if FE_DELAYED_PADDING
if (totlen < (ETHER_MIN_LEN - ETHER_CRC_LEN))
sc->txb_padding = (ETHER_MIN_LEN - ETHER_CRC_LEN) - totlen;
#endif
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
for (; m != NULL; m = m->m_next) {
if (m->m_len) {
bus_space_write_multi_1(bst, bsh, FE_BMPR8,
mtod(m, uint8_t *), m->m_len);
}
}
} else {
uint8_t *data, savebyte[2];
int leftover;
leftover = 0;
savebyte[0] = savebyte[1] = 0;
for (; m != NULL; m = m->m_next) {
len = m->m_len;
if (len == 0)
continue;
data = mtod(m, uint8_t *);
while (len > 0) {
if (leftover) {
savebyte[1] = *data++;
len--;
bus_space_write_stream_2(bst, bsh,
FE_BMPR8, *(uint16_t *)savebyte);
leftover = 0;
} else if (BUS_SPACE_ALIGNED_POINTER(data,
uint16_t) == 0) {
savebyte[0] = *data++;
len--;
leftover = 1;
} else {
leftover = len & 1;
len &= ~1;
bus_space_write_multi_stream_2(bst,
bsh, FE_BMPR8, (uint16_t *)data,
len >> 1);
data += len;
if (leftover)
savebyte[0] = *data++;
len = 0;
}
}
if (len < 0)
panic("mb86960_write_mbufs: negative len");
}
if (leftover) {
savebyte[1] = 0;
bus_space_write_stream_2(bst, bsh, FE_BMPR8,
*(uint16_t *)savebyte);
}
}
#if FE_DELAYED_PADDING == 0
len = (ETHER_MIN_LEN - ETHER_CRC_LEN) - totlen;
if (len > 0) {
if (sc->sc_flags & FE_FLAGS_SBW_BYTE) {
while (len-- > 0)
bus_space_write_1(bst, bsh, FE_BMPR8, 0);
} else {
len >>= 1;
while (len-- > 0)
bus_space_write_2(bst, bsh, FE_BMPR8, 0);
}
}
#endif
}
void
mb86960_getmcaf(struct ethercom *ec, uint8_t *af)
{
struct ifnet *ifp = &ec->ec_if;
struct ether_multi *enm;
uint32_t crc;
struct ether_multistep step;
if ((ifp->if_flags & IFF_PROMISC) != 0)
goto allmulti;
memset(af, 0, FE_FILTER_LEN);
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
sizeof(enm->enm_addrlo)) != 0) {
ETHER_UNLOCK(ec);
goto allmulti;
}
crc = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN);
crc >>= 26;
af[crc >> 3] |= 1 << (crc & 7);
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
ifp->if_flags &= ~IFF_ALLMULTI;
return;
allmulti:
ifp->if_flags |= IFF_ALLMULTI;
memset(af, 0xff, FE_FILTER_LEN);
}
void
mb86960_setmode(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
int flags = sc->sc_ec.ec_if.if_flags;
if ((flags & IFF_RUNNING) == 0)
return;
if ((flags & IFF_PROMISC) != 0) {
bus_space_write_1(bst, bsh, FE_DLCR5,
sc->proto_dlcr5 | FE_D5_AFM0 | FE_D5_AFM1);
sc->filter_change = 0;
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: promiscuous mode\n",
device_xname(sc->sc_dev));
#endif
return;
}
bus_space_write_1(bst, bsh, FE_DLCR5, sc->proto_dlcr5 | FE_D5_AFM1);
mb86960_getmcaf(&sc->sc_ec, sc->filter);
sc->filter_change = 1;
#if FE_DEBUG >= 3
log(LOG_INFO,
"%s: address filter: [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
device_xname(sc->sc_dev),
sc->filter[0], sc->filter[1], sc->filter[2], sc->filter[3],
sc->filter[4], sc->filter[5], sc->filter[6], sc->filter[7]);
#endif
if (sc->txb_sched == 0 && sc->txb_count == 0 &&
(bus_space_read_1(bst, bsh, FE_DLCR1) & FE_D1_PKTRDY) == 0) {
mb86960_loadmar(sc);
} else {
#if FE_DEBUG >= 4
log(LOG_INFO, "%s: filter change delayed\n",
device_xname(sc->sc_dev));
#endif
}
}
void
mb86960_loadmar(struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_DISABLE);
bus_space_write_1(bst, bsh, FE_DLCR7,
sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP);
bus_space_write_region_1(bst, bsh, FE_MAR8, sc->filter, FE_FILTER_LEN);
bus_space_write_1(bst, bsh, FE_DLCR7,
sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP);
bus_space_write_1(bst, bsh, FE_DLCR6,
sc->proto_dlcr6 | FE_D6_DLC_ENABLE);
sc->filter_change = 0;
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: address filter changed\n", device_xname(sc->sc_dev));
#endif
}
int
mb86960_enable(struct mb86960_softc *sc)
{
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: mb86960_enable()\n", device_xname(sc->sc_dev));
#endif
if ((sc->sc_stat & FE_STAT_ENABLED) == 0 && sc->sc_enable != NULL) {
if ((*sc->sc_enable)(sc) != 0) {
aprint_error_dev(sc->sc_dev, "device enable failed\n");
return EIO;
}
}
sc->sc_stat |= FE_STAT_ENABLED;
return 0;
}
void
mb86960_disable(struct mb86960_softc *sc)
{
#if FE_DEBUG >= 3
log(LOG_INFO, "%s: mb86960_disable()\n", device_xname(sc->sc_dev));
#endif
if ((sc->sc_stat & FE_STAT_ENABLED) != 0 && sc->sc_disable != NULL) {
(*sc->sc_disable)(sc);
sc->sc_stat &= ~FE_STAT_ENABLED;
}
}
int
mb86960_activate(device_t self, enum devact act)
{
struct mb86960_softc *sc = device_private(self);
switch (act) {
case DVACT_DEACTIVATE:
if_deactivate(&sc->sc_ec.ec_if);
return 0;
default:
return EOPNOTSUPP;
}
}
int
mb86960_detach(struct mb86960_softc *sc)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
if ((sc->sc_stat & FE_STAT_ATTACHED) == 0)
return 0;
rnd_detach_source(&sc->rnd_source);
ether_ifdetach(ifp);
if_detach(ifp);
ifmedia_fini(&sc->sc_media);
mb86960_disable(sc);
return 0;
}
void
mb86965_read_eeprom(bus_space_tag_t iot, bus_space_handle_t ioh, uint8_t *data)
{
int addr, op, bit;
uint16_t val;
for (addr = 0; addr < FE_EEPROM_SIZE / 2; addr++) {
bus_space_write_1(iot, ioh, FE_BMPR16, 0x00);
bus_space_write_1(iot, ioh, FE_BMPR17, 0x00);
bus_space_write_1(iot, ioh, FE_BMPR16, FE_B16_SELECT);
bus_space_write_1(iot, ioh, FE_BMPR17, FE_B17_DATA);
FE_EEPROM_DELAY();
bus_space_write_1(iot, ioh,
FE_BMPR16, FE_B16_SELECT | FE_B16_CLOCK);
FE_EEPROM_DELAY();
bus_space_write_1(iot, ioh, FE_BMPR16, FE_B16_SELECT);
op = 0x80 | addr;
for (bit = 8; bit > 0; bit--) {
bus_space_write_1(iot, ioh, FE_BMPR17,
(op & (1 << (bit - 1))) ? FE_B17_DATA : 0);
FE_EEPROM_DELAY();
bus_space_write_1(iot, ioh,
FE_BMPR16, FE_B16_SELECT | FE_B16_CLOCK);
FE_EEPROM_DELAY();
bus_space_write_1(iot, ioh, FE_BMPR16, FE_B16_SELECT);
}
bus_space_write_1(iot, ioh, FE_BMPR17, 0x00);
val = 0;
for (bit = 16; bit > 0; bit--) {
FE_EEPROM_DELAY();
bus_space_write_1(iot, ioh,
FE_BMPR16, FE_B16_SELECT | FE_B16_CLOCK);
FE_EEPROM_DELAY();
if (bus_space_read_1(iot, ioh, FE_BMPR17) &
FE_B17_DATA)
val |= 1 << (bit - 1);
bus_space_write_1(iot, ioh,
FE_BMPR16, FE_B16_SELECT);
}
data[addr * 2] = val >> 8;
data[addr * 2 + 1] = val & 0xff;
}
bus_space_write_1(iot, ioh, FE_BMPR16, 0);
bus_space_write_1(iot, ioh, FE_BMPR17, 0);
#if FE_DEBUG >= 3
log(LOG_INFO, "mb86965_read_eeprom: "
" %02x%02x%02x%02x %02x%02x%02x%02x -"
" %02x%02x%02x%02x %02x%02x%02x%02x -"
" %02x%02x%02x%02x %02x%02x%02x%02x -"
" %02x%02x%02x%02x %02x%02x%02x%02x\n",
data[ 0], data[ 1], data[ 2], data[ 3],
data[ 4], data[ 5], data[ 6], data[ 7],
data[ 8], data[ 9], data[10], data[11],
data[12], data[13], data[14], data[15],
data[16], data[17], data[18], data[19],
data[20], data[21], data[22], data[23],
data[24], data[25], data[26], data[27],
data[28], data[29], data[30], data[31]);
#endif
}
#if FE_DEBUG >= 1
void
mb86960_dump(int level, struct mb86960_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
uint8_t save_dlcr7;
save_dlcr7 = bus_space_read_1(bst, bsh, FE_DLCR7);
log(level, "\tDLCR = %02x %02x %02x %02x %02x %02x %02x %02x\n",
bus_space_read_1(bst, bsh, FE_DLCR0),
bus_space_read_1(bst, bsh, FE_DLCR1),
bus_space_read_1(bst, bsh, FE_DLCR2),
bus_space_read_1(bst, bsh, FE_DLCR3),
bus_space_read_1(bst, bsh, FE_DLCR4),
bus_space_read_1(bst, bsh, FE_DLCR5),
bus_space_read_1(bst, bsh, FE_DLCR6),
bus_space_read_1(bst, bsh, FE_DLCR7));
bus_space_write_1(bst, bsh, FE_DLCR7,
(save_dlcr7 & ~FE_D7_RBS) | FE_D7_RBS_DLCR);
log(level, "\t %02x %02x %02x %02x %02x %02x %02x %02x\n",
bus_space_read_1(bst, bsh, FE_DLCR8),
bus_space_read_1(bst, bsh, FE_DLCR9),
bus_space_read_1(bst, bsh, FE_DLCR10),
bus_space_read_1(bst, bsh, FE_DLCR11),
bus_space_read_1(bst, bsh, FE_DLCR12),
bus_space_read_1(bst, bsh, FE_DLCR13),
bus_space_read_1(bst, bsh, FE_DLCR14),
bus_space_read_1(bst, bsh, FE_DLCR15));
bus_space_write_1(bst, bsh, FE_DLCR7,
(save_dlcr7 & ~FE_D7_RBS) | FE_D7_RBS_MAR);
log(level, "\tMAR = %02x %02x %02x %02x %02x %02x %02x %02x\n",
bus_space_read_1(bst, bsh, FE_MAR8),
bus_space_read_1(bst, bsh, FE_MAR9),
bus_space_read_1(bst, bsh, FE_MAR10),
bus_space_read_1(bst, bsh, FE_MAR11),
bus_space_read_1(bst, bsh, FE_MAR12),
bus_space_read_1(bst, bsh, FE_MAR13),
bus_space_read_1(bst, bsh, FE_MAR14),
bus_space_read_1(bst, bsh, FE_MAR15));
bus_space_write_1(bst, bsh, FE_DLCR7,
(save_dlcr7 & ~FE_D7_RBS) | FE_D7_RBS_BMPR);
log(level,
"\tBMPR = xx xx %02x %02x %02x %02x %02x %02x %02x %02x xx %02x\n",
bus_space_read_1(bst, bsh, FE_BMPR10),
bus_space_read_1(bst, bsh, FE_BMPR11),
bus_space_read_1(bst, bsh, FE_BMPR12),
bus_space_read_1(bst, bsh, FE_BMPR13),
bus_space_read_1(bst, bsh, FE_BMPR14),
bus_space_read_1(bst, bsh, FE_BMPR15),
bus_space_read_1(bst, bsh, FE_BMPR16),
bus_space_read_1(bst, bsh, FE_BMPR17),
bus_space_read_1(bst, bsh, FE_BMPR19));
bus_space_write_1(bst, bsh, FE_DLCR7, save_dlcr7);
}
#endif