#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mb86950.c,v 1.35 2021/07/31 14:36:33 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 <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 <sys/bus.h>
#include <dev/ic/mb86950reg.h>
#include <dev/ic/mb86950var.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
int mb86950_ioctl(struct ifnet *, u_long, void *);
void mb86950_init(struct mb86950_softc *);
void mb86950_start(struct ifnet *);
void mb86950_watchdog(struct ifnet *);
void mb86950_reset(struct mb86950_softc *);
void mb86950_stop(struct mb86950_softc *);
void mb86950_tint(struct mb86950_softc *, u_int8_t);
void mb86950_rint(struct mb86950_softc *, u_int8_t);
int mb86950_get_fifo(struct mb86950_softc *, u_int);
ushort mb86950_put_fifo(struct mb86950_softc *, struct mbuf *);
void mb86950_drain_fifo(struct mb86950_softc *);
int mb86950_mediachange(struct ifnet *);
void mb86950_mediastatus(struct ifnet *, struct ifmediareq *);
#if ESTAR_DEBUG >= 1
void mb86950_dump(int, struct mb86950_softc *);
#endif
void
mb86950_attach(struct mb86950_softc *sc, u_int8_t *myea)
{
#ifdef DIAGNOSTIC
if (myea == NULL) {
printf("%s: ethernet address shouldn't be NULL\n",
device_xname(sc->sc_dev));
panic("NULL ethernet address");
}
#endif
mb86950_stop(sc);
memcpy(sc->sc_enaddr, myea, sizeof(sc->sc_enaddr));
sc->sc_stat |= ESTAR_STAT_ENABLED;
}
void
mb86950_stop(struct mb86950_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
bus_space_write_1(bst, bsh, DLCR_CONFIG, DISABLE_DLC);
delay(200);
bus_space_write_1(bst, bsh, DLCR_TX_INT_EN, 0);
bus_space_write_1(bst, bsh, DLCR_RX_INT_EN, 0);
bus_space_write_1(bst, bsh, DLCR_TX_STAT, 0xff);
bus_space_write_1(bst, bsh, DLCR_RX_STAT, 0xff);
bus_space_write_2(bst, bsh, BMPR_DMA, 0);
bus_space_write_1(bst, bsh, DLCR_TX_MODE, 0);
bus_space_write_1(bst, bsh, DLCR_RX_MODE, 0);
mb86950_drain_fifo(sc);
}
void
mb86950_drain_fifo(struct mb86950_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
while (!(bus_space_read_1(bst, bsh, DLCR_RX_STAT) & RX_BUS_RD_ERR))
bus_space_read_2(bst, bsh, BMPR_FIFO);
bus_space_write_1(bst, bsh, DLCR_RX_STAT, RX_BUS_RD_ERR);
}
void
mb86950_config(struct mb86950_softc *sc, int *media, int nmedia, int defmedia)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_start = mb86950_start;
ifp->if_ioctl = mb86950_ioctl;
ifp->if_watchdog = mb86950_watchdog;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
IFQ_SET_READY(&ifp->if_snd);
sc->sc_ec.ec_ifmedia = &sc->sc_media;
ifmedia_init(&sc->sc_media, 0, mb86950_mediachange,
mb86950_mediastatus);
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);
bus_space_write_region_1(bst, bsh, DLCR_NODE_ID, sc->sc_enaddr,
ETHER_ADDR_LEN);
ether_ifattach(ifp, sc->sc_enaddr);
rnd_attach_source(&sc->rnd_source, device_xname(sc->sc_dev),
RND_TYPE_NET, RND_FLAG_DEFAULT);
if (sc->txb_num_pkt == 0) sc->txb_num_pkt = 1;
if (sc->rxb_num_pkt == 0) sc->rxb_num_pkt = 100;
printf("%s: Ethernet address %s\n", device_xname(sc->sc_dev),
ether_sprintf(sc->sc_enaddr));
sc->sc_stat |= ESTAR_STAT_ATTACHED;
}
int
mb86950_mediachange(struct ifnet *ifp)
{
struct mb86950_softc *sc = ifp->if_softc;
if (sc->sc_mediachange)
return (*sc->sc_mediachange)(sc);
return 0;
}
void
mb86950_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct mb86950_softc *sc = ifp->if_softc;
if ((sc->sc_stat & ESTAR_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
mb86950_reset(struct mb86950_softc *sc)
{
int s;
s = splnet();
log(LOG_ERR, "%s: device reset\n", device_xname(sc->sc_dev));
mb86950_stop(sc);
mb86950_init(sc);
splx(s);
}
void
mb86950_watchdog(struct ifnet *ifp)
{
struct mb86950_softc *sc = ifp->if_softc;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int8_t tstat;
if ((tstat = bus_space_read_1(bst, bsh, DLCR_TX_STAT)) != 0) {
if (tstat & TX_CR_LOST) {
if ((tstat & (TX_COL | TX_16COL)) == 0) {
log(LOG_ERR, "%s: carrier lost\n",
device_xname(sc->sc_dev));
} else {
log(LOG_ERR, "%s: excessive collisions\n",
device_xname(sc->sc_dev));
}
}
else if ((tstat & (TX_UNDERFLO | TX_BUS_WR_ERR)) != 0) {
log(LOG_ERR, "%s: tx fifo underflow/overflow\n",
device_xname(sc->sc_dev));
} else {
log(LOG_ERR, "%s: transmit error\n",
device_xname(sc->sc_dev));
}
} else
log(LOG_ERR, "%s: device timeout\n", device_xname(sc->sc_dev));
if_statinc(ifp, if_oerrors);
mb86950_reset(sc);
}
int
mb86950_ioctl(struct ifnet *ifp, unsigned long cmd, void *data)
{
struct mb86950_softc *sc = ifp->if_softc;
struct ifaddr *ifa = (struct ifaddr *)data;
int s, error = 0;
s = splnet();
switch (cmd) {
case SIOCINITIFADDR:
if ((error = mb86950_enable(sc)) != 0)
break;
ifp->if_flags |= IFF_UP;
mb86950_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:
mb86950_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
mb86950_disable(sc);
break;
case IFF_UP:
if ((error = mb86950_enable(sc)) != 0)
break;
mb86950_init(sc);
break;
case IFF_UP|IFF_RUNNING:
#if 0
mb86950_setmode(sc);
#endif
break;
case 0:
break;
}
#if ESTAR_DEBUG >= 1
if (ifp->if_flags & IFF_DEBUG) {
log(LOG_INFO, "%s: SIOCSIFFLAGS(DEBUG)\n",
device_xname(sc->sc_dev));
mb86950_dump(LOG_DEBUG, sc);
}
#endif
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
splx(s);
return error;
}
void
mb86950_init(struct mb86950_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;
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
sc->txb_sched = 0;
bus_space_write_1(bst, bsh, DLCR_TX_MODE, LBC);
bus_space_write_1(bst, bsh, DLCR_RX_MODE, NORMAL_MODE);
bus_space_write_1(bst, bsh, DLCR_TX_INT_EN, TX_MASK);
bus_space_write_1(bst, bsh, DLCR_RX_INT_EN, RX_MASK);
bus_space_write_1(bst, bsh, DLCR_CONFIG, ENABLE_DLC);
delay(200);
ifp->if_flags |= IFF_RUNNING;
mb86950_start(ifp);
}
void
mb86950_start(struct ifnet *ifp)
{
struct mb86950_softc *sc = ifp->if_softc;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct mbuf *m;
int len;
if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
IF_DEQUEUE(&ifp->if_snd, m);
if (m == 0)
return;
bpf_mtap(ifp, m, BPF_D_OUT);
len = mb86950_put_fifo(sc,m);
m_freem(m);
if (bus_space_read_1(bst, bsh, DLCR_TX_STAT)
& (TX_UNDERFLO | TX_BUS_WR_ERR)) {
log(LOG_ERR, "%s: tx fifo underflow/overflow\n",
device_xname(sc->sc_dev));
}
bus_space_write_2(bst, bsh, BMPR_TX_LENGTH, len | TRANSMIT_START);
bus_space_write_1(bst, bsh, DLCR_TX_INT_EN, TX_MASK);
sc->txb_sched++;
if (sc->txb_sched > sc->txb_num_pkt) {
ifp->if_flags |= IFF_OACTIVE;
ifp->if_timer = 2;
}
}
u_short
mb86950_put_fifo(struct mb86950_softc *sc, struct mbuf *m)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_short *data;
u_char savebyte[2];
int len, len1, wantbyte;
u_short totlen;
memset(savebyte, 0, sizeof(savebyte));
totlen = wantbyte = 0;
for (; m != NULL; m = m->m_next) {
data = mtod(m, u_short *);
len = m->m_len;
if (len > 0) {
totlen += len;
if (wantbyte) {
savebyte[1] = *((u_char *)data);
bus_space_write_2(bst, bsh, BMPR_FIFO,
*savebyte);
data = (u_short *)((u_char *)data + 1);
len--;
wantbyte = 0;
}
if (len > 1) {
len1 = len/2;
bus_space_write_multi_stream_2(bst, bsh,
BMPR_FIFO, data, len1);
data += len1;
len &= 1;
}
if (len == 1) {
savebyte[0] = *((u_char *)data);
wantbyte = 1;
}
}
}
if (wantbyte) {
savebyte[1] = 0;
bus_space_write_2(bst, bsh, BMPR_FIFO, *savebyte);
}
if (totlen < (ETHER_MIN_LEN - ETHER_CRC_LEN)) {
for (len = totlen + 1; len < (ETHER_MIN_LEN - ETHER_CRC_LEN);
len += 2)
bus_space_write_2(bst, bsh, BMPR_FIFO, 0);
totlen = (ETHER_MIN_LEN - ETHER_CRC_LEN);
}
return totlen;
}
int
mb86950_intr(void *arg)
{
struct mb86950_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;
u_int8_t tstat, rstat;
tstat = bus_space_read_1(bst, bsh, DLCR_TX_STAT);
rstat = bus_space_read_1(bst, bsh, DLCR_RX_STAT);
if (tstat == 0 && rstat == 0) return 0;
bus_space_write_1(bst, bsh, DLCR_TX_INT_EN, 0);
bus_space_write_1(bst, bsh, DLCR_RX_INT_EN, 0);
if (tstat != 0) {
mb86950_tint(sc, tstat);
bus_space_write_1(bst, bsh, DLCR_TX_STAT, tstat);
}
if (rstat != 0) {
mb86950_rint(sc, rstat);
bus_space_write_1(bst, bsh, DLCR_RX_STAT, rstat);
}
if (sc->txb_sched > 0)
bus_space_write_1(bst, bsh, DLCR_TX_INT_EN, TX_MASK);
if ((ifp->if_flags & IFF_OACTIVE) == 0)
if_schedule_deferred_start(ifp);
bus_space_write_1(bst, bsh, DLCR_RX_INT_EN, RX_MASK);
return 1;
}
void
mb86950_tint(struct mb86950_softc *sc, u_int8_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 col;
if (tstat & (TX_UNDERFLO | TX_BUS_WR_ERR)) {
if_statinc(ifp, if_oerrors);
}
if (tstat & TX_16COL) {
if_statadd(ifp, if_collisions, 16);
if (sc->txb_sched > 0)
sc->txb_sched--;
}
if (tstat & TX_DONE) {
if_statinc(ifp, if_opackets);
if (sc->txb_sched > 0)
sc->txb_sched--;
if (tstat & TX_COL) {
col = (bus_space_read_1(bst, bsh, DLCR_TX_MODE)
& COL_MASK) >> 4;
if_statadd(ifp, if_collisions, col);
}
}
if (sc->txb_sched == 0) {
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
}
}
void
mb86950_rint(struct mb86950_softc *sc, u_int8_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 & RX_ERR_MASK) {
if ((rstat & RX_BUS_RD_ERR) == 0) {
if_statinc(ifp, if_ierrors);
}
}
for (i = 0; i < sc->rxb_num_pkt; i++) {
if (bus_space_read_1(bst, bsh, DLCR_RX_MODE) & RX_BUF_EMTY)
break;
status = bus_space_read_2(bst, bsh, BMPR_FIFO);
if ((status & GOOD_PKT) == 0) {
if_statinc(ifp, if_ierrors);
mb86950_drain_fifo(sc);
continue;
}
len = bus_space_read_2(bst, bsh, BMPR_FIFO);
if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN)
|| len < ETHER_HDR_LEN) {
if_statinc(ifp, if_ierrors);
mb86950_drain_fifo(sc);
continue;
}
if (mb86950_get_fifo(sc, len) != 0) {
if_statinc(ifp, if_ierrors);
mb86950_drain_fifo(sc);
return;
}
}
}
int
mb86950_get_fifo(struct mb86950_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 -1;
if (len & 1)
len++;
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 -1;
}
}
m->m_data += EOFF;
m->m_len = len;
bus_space_read_multi_stream_2(bst, bsh, BMPR_FIFO,
mtod(m, u_int16_t *), (len + 1) >> 1);
if_percpuq_enqueue(ifp->if_percpuq, m);
return 0;
}
int
mb86950_enable(struct mb86950_softc *sc)
{
if ((sc->sc_stat & ESTAR_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 |= ESTAR_STAT_ENABLED;
return 0;
}
void
mb86950_disable(struct mb86950_softc *sc)
{
if ((sc->sc_stat & ESTAR_STAT_ENABLED) != 0 && sc->sc_disable != NULL) {
(*sc->sc_disable)(sc);
sc->sc_stat &= ~ESTAR_STAT_ENABLED;
}
}
int
mb86950_activate(device_t self, enum devact act)
{
struct mb86950_softc *sc = device_private(self);
switch (act) {
case DVACT_DEACTIVATE:
if_deactivate(&sc->sc_ec.ec_if);
return 0;
default:
return EOPNOTSUPP;
}
}
int
mb86950_detach(struct mb86950_softc *sc)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
if ((sc->sc_stat & ESTAR_STAT_ATTACHED) == 0)
return 0;
rnd_detach_source(&sc->rnd_source);
ether_ifdetach(ifp);
if_detach(ifp);
ifmedia_fini(&sc->sc_media);
return 0;
}
#if ESTAR_DEBUG >= 1
void
mb86950_dump(int level, struct mb86950_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
log(level, "\tDLCR = %02x %02x %02x %02x %02x %02x %02x\n",
bus_space_read_1(bst, bsh, DLCR_TX_STAT),
bus_space_read_1(bst, bsh, DLCR_TX_INT_EN),
bus_space_read_1(bst, bsh, DLCR_RX_STAT),
bus_space_read_1(bst, bsh, DLCR_RX_INT_EN),
bus_space_read_1(bst, bsh, DLCR_TX_MODE),
bus_space_read_1(bst, bsh, DLCR_RX_MODE),
bus_space_read_1(bst, bsh, DLCR_CONFIG));
}
#endif