#include <sys/param.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/lock.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_var.h>
#include <net/ifq_var.h>
#include <dev/netif/lnc/lancereg.h>
#include <dev/netif/lnc/lancevar.h>
#include <dev/netif/lnc/am79900reg.h>
#include <dev/netif/lnc/am79900var.h>
static void am79900_meminit(struct lance_softc *);
static void am79900_rint(struct lance_softc *);
static void am79900_tint(struct lance_softc *);
static void am79900_start_locked(struct lance_softc *sc);
#ifdef LEDEBUG
static void am79900_recv_print(struct lance_softc *, int);
static void am79900_xmit_print(struct lance_softc *, int);
#endif
int
am79900_config(struct am79900_softc *sc, const char* name, int unit)
{
int mem;
sc->lsc.sc_meminit = am79900_meminit;
sc->lsc.sc_start_locked = am79900_start_locked;
lance_config(&sc->lsc, name, unit);
mem = 0;
sc->lsc.sc_initaddr = mem;
mem += sizeof(struct leinit);
sc->lsc.sc_rmdaddr = mem;
mem += sizeof(struct lermd) * sc->lsc.sc_nrbuf;
sc->lsc.sc_tmdaddr = mem;
mem += sizeof(struct letmd) * sc->lsc.sc_ntbuf;
sc->lsc.sc_rbufaddr = mem;
mem += LEBLEN * sc->lsc.sc_nrbuf;
sc->lsc.sc_tbufaddr = mem;
mem += LEBLEN * sc->lsc.sc_ntbuf;
if (mem > sc->lsc.sc_memsize)
panic("%s: memsize", __func__);
return (0);
}
void
am79900_detach(struct am79900_softc *sc)
{
ether_ifdetach(sc->lsc.ifp);
}
static void
am79900_meminit(struct lance_softc *sc)
{
struct ifnet *ifp = sc->ifp;
struct leinit init;
struct lermd rmd;
struct letmd tmd;
u_long a;
int bix;
if (ifp->if_flags & IFF_PROMISC)
init.init_mode = LE_HTOLE32(LE_MODE_NORMAL | LE_MODE_PROM);
else
init.init_mode = LE_HTOLE32(LE_MODE_NORMAL);
init.init_mode |= LE_HTOLE32(((ffs(sc->sc_ntbuf) - 1) << 28) |
((ffs(sc->sc_nrbuf) - 1) << 20));
init.init_padr[0] = LE_HTOLE32(sc->sc_enaddr[0] |
(sc->sc_enaddr[1] << 8) | (sc->sc_enaddr[2] << 16) |
(sc->sc_enaddr[3] << 24));
init.init_padr[1] = LE_HTOLE32(sc->sc_enaddr[4] |
(sc->sc_enaddr[5] << 8));
lance_setladrf(sc, init.init_ladrf);
sc->sc_last_rd = 0;
sc->sc_first_td = sc->sc_last_td = sc->sc_no_td = 0;
a = sc->sc_addr + LE_RMDADDR(sc, 0);
init.init_rdra = LE_HTOLE32(a);
a = sc->sc_addr + LE_TMDADDR(sc, 0);
init.init_tdra = LE_HTOLE32(a);
(*sc->sc_copytodesc)(sc, &init, LE_INITADDR(sc), sizeof(init));
for (bix = 0; bix < sc->sc_nrbuf; bix++) {
a = sc->sc_addr + LE_RBUFADDR(sc, bix);
rmd.rmd0 = LE_HTOLE32(a);
rmd.rmd1 = LE_HTOLE32(LE_R1_OWN | LE_R1_ONES |
(-LEBLEN & 0xfff));
rmd.rmd2 = 0;
rmd.rmd3 = 0;
(*sc->sc_copytodesc)(sc, &rmd, LE_RMDADDR(sc, bix),
sizeof(rmd));
}
for (bix = 0; bix < sc->sc_ntbuf; bix++) {
a = sc->sc_addr + LE_TBUFADDR(sc, bix);
tmd.tmd0 = LE_HTOLE32(a);
tmd.tmd1 = LE_HTOLE32(LE_T1_ONES);
tmd.tmd2 = 0;
tmd.tmd3 = 0;
(*sc->sc_copytodesc)(sc, &tmd, LE_TMDADDR(sc, bix),
sizeof(tmd));
}
}
static void
am79900_rint(struct lance_softc *sc)
{
struct ifnet *ifp = sc->ifp;
struct mbuf *m;
struct lermd rmd;
uint32_t rmd1;
int bix, rp;
#if defined(__x86_64__)
struct ether_header *eh;
#endif
bix = sc->sc_last_rd;
for (;;) {
rp = LE_RMDADDR(sc, bix);
(*sc->sc_copyfromdesc)(sc, &rmd, rp, sizeof(rmd));
rmd1 = LE_LE32TOH(rmd.rmd1);
if (rmd1 & LE_R1_OWN)
break;
m = NULL;
if ((rmd1 & (LE_R1_ERR | LE_R1_STP | LE_R1_ENP)) !=
(LE_R1_STP | LE_R1_ENP)){
if (rmd1 & LE_R1_ERR) {
#ifdef LEDEBUG
if (rmd1 & LE_R1_ENP) {
if ((rmd1 & LE_R1_OFLO) == 0) {
if (rmd1 & LE_R1_FRAM)
if_printf(ifp,
"framing error\n");
if (rmd1 & LE_R1_CRC)
if_printf(ifp,
"crc mismatch\n");
}
} else
if (rmd1 & LE_R1_OFLO)
if_printf(ifp, "overflow\n");
#endif
if (rmd1 & LE_R1_BUFF)
if_printf(ifp,
"receive buffer error\n");
} else if ((rmd1 & (LE_R1_STP | LE_R1_ENP)) !=
(LE_R1_STP | LE_R1_ENP))
if_printf(ifp, "dropping chained buffer\n");
} else {
#ifdef LEDEBUG
if (sc->sc_flags & LE_DEBUG)
am79900_recv_print(sc, bix);
#endif
m = lance_get(sc, LE_RBUFADDR(sc, bix),
(LE_LE32TOH(rmd.rmd2) & 0xfff) - ETHER_CRC_LEN);
}
rmd.rmd1 = LE_HTOLE32(LE_R1_OWN | LE_R1_ONES |
(-LEBLEN & 0xfff));
rmd.rmd2 = 0;
rmd.rmd3 = 0;
(*sc->sc_copytodesc)(sc, &rmd, rp, sizeof(rmd));
if (++bix == sc->sc_nrbuf)
bix = 0;
if (m != NULL) {
IFNET_STAT_INC(ifp, ipackets, 1);
#ifdef __x86_64__
eh = mtod(m, struct ether_header *);
if (memcmp(eh->ether_shost, sc->sc_enaddr,
ETHER_ADDR_LEN) == 0) {
m_freem(m);
continue;
}
#endif
ifp->if_input(ifp, m, NULL, -1);
} else
IFNET_STAT_INC(ifp, ierrors, 1);
}
sc->sc_last_rd = bix;
}
static void
am79900_tint(struct lance_softc *sc)
{
struct ifnet *ifp = sc->ifp;
struct letmd tmd;
uint32_t tmd1, tmd2;
int bix;
bix = sc->sc_first_td;
for (;;) {
if (sc->sc_no_td <= 0)
break;
(*sc->sc_copyfromdesc)(sc, &tmd, LE_TMDADDR(sc, bix),
sizeof(tmd));
tmd1 = LE_LE32TOH(tmd.tmd1);
#ifdef LEDEBUG
if (sc->sc_flags & LE_DEBUG)
if_printf(ifp, "trans tmd: "
"adr %08x, flags/blen %08x\n",
LE_LE32TOH(tmd.tmd0), tmd1);
#endif
if (tmd1 & LE_T1_OWN)
break;
ifq_clr_oactive(&ifp->if_snd);
if (tmd1 & LE_T1_ERR) {
tmd2 = LE_LE32TOH(tmd.tmd2);
if (tmd2 & LE_T2_BUFF)
if_printf(ifp, "transmit buffer error\n");
else if (tmd2 & LE_T2_UFLO)
if_printf(ifp, "underflow\n");
if (tmd2 & (LE_T2_BUFF | LE_T2_UFLO)) {
lance_init_locked(sc);
return;
}
if (tmd2 & LE_T2_LCAR) {
if (sc->sc_flags & LE_CARRIER) {
ifp->if_link_state = LINK_STATE_DOWN;
if_link_state_change(ifp);
}
sc->sc_flags &= ~LE_CARRIER;
if (sc->sc_nocarrier)
(*sc->sc_nocarrier)(sc);
else
if_printf(ifp, "lost carrier\n");
}
if (tmd2 & LE_T2_LCOL)
IFNET_STAT_INC(ifp, collisions, 1);
if (tmd2 & LE_T2_RTRY) {
#ifdef LEDEBUG
if_printf(ifp, "excessive collisions\n");
#endif
IFNET_STAT_INC(ifp, collisions, 16);
}
IFNET_STAT_INC(ifp, oerrors, 1);
} else {
if (tmd1 & LE_T1_ONE)
IFNET_STAT_INC(ifp, collisions, 1);
else if (tmd1 & LE_T1_MORE)
IFNET_STAT_INC(ifp, collisions, 2);
IFNET_STAT_INC(ifp, opackets, 1);
}
if (++bix == sc->sc_ntbuf)
bix = 0;
--sc->sc_no_td;
}
sc->sc_first_td = bix;
ifp->if_timer = sc->sc_no_td > 0 ? 5 : 0;
}
void
am79900_intr(void *arg)
{
struct lance_softc *sc = arg;
struct ifnet *ifp = sc->ifp;
uint16_t isr;
if (sc->sc_hwintr && (*sc->sc_hwintr)(sc) == -1) {
IFNET_STAT_INC(ifp, ierrors, 1);
lance_init_locked(sc);
return;
}
isr = (*sc->sc_rdcsr)(sc, LE_CSR0);
#if defined(LEDEBUG) && LEDEBUG > 1
if (sc->sc_flags & LE_DEBUG)
if_printf(ifp, "%s: entering with isr=%04x\n", __func__, isr);
#endif
if ((isr & LE_C0_INTR) == 0) {
return;
}
(*sc->sc_wrcsr)(sc, LE_CSR0, isr & ~(LE_C0_INEA | LE_C0_TDMD |
LE_C0_STOP | LE_C0_STRT | LE_C0_INIT));
if (isr & LE_C0_ERR) {
if (isr & LE_C0_BABL) {
#ifdef LEDEBUG
if_printf(ifp, "babble\n");
#endif
IFNET_STAT_INC(ifp, oerrors, 1);
}
#if 0
if (isr & LE_C0_CERR) {
if_printf(ifp, "collision error\n");
ifp->if_collisions++;
}
#endif
if (isr & LE_C0_MISS) {
#ifdef LEDEBUG
if_printf(ifp, "missed packet\n");
#endif
IFNET_STAT_INC(ifp, ierrors, 1);
}
if (isr & LE_C0_MERR) {
if_printf(ifp, "memory error\n");
lance_init_locked(sc);
return;
}
}
if ((isr & LE_C0_RXON) == 0) {
if_printf(ifp, "receiver disabled\n");
IFNET_STAT_INC(ifp, ierrors, 1);
lance_init_locked(sc);
return;
}
if ((isr & LE_C0_TXON) == 0) {
if_printf(ifp, "transmitter disabled\n");
IFNET_STAT_INC(ifp, oerrors, 1);
lance_init_locked(sc);
return;
}
if (!(sc->sc_flags & LE_CARRIER)) {
ifp->if_link_state = LINK_STATE_UP;
if_link_state_change(ifp);
}
sc->sc_flags |= LE_CARRIER;
if (isr & LE_C0_RINT)
am79900_rint(sc);
if (isr & LE_C0_TINT)
am79900_tint(sc);
(*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_INEA);
if (!ifq_is_empty(&ifp->if_snd))
if_devstart(ifp);
}
static void
am79900_start_locked(struct lance_softc *sc)
{
struct ifnet *ifp = sc->ifp;
struct letmd tmd;
struct mbuf *m;
int bix, enq, len, rp;
if ((ifp->if_flags & IFF_RUNNING) == 0 || ifq_is_oactive(&ifp->if_snd))
return;
bix = sc->sc_last_td;
enq = 0;
for (; sc->sc_no_td < sc->sc_ntbuf &&
!ifq_is_empty(&ifp->if_snd);) {
rp = LE_TMDADDR(sc, bix);
(*sc->sc_copyfromdesc)(sc, &tmd, rp, sizeof(tmd));
if (LE_LE32TOH(tmd.tmd1) & LE_T1_OWN) {
ifq_set_oactive(&ifp->if_snd);
if_printf(ifp,
"missing buffer, no_td = %d, last_td = %d\n",
sc->sc_no_td, sc->sc_last_td);
}
m = ifq_dequeue(&ifp->if_snd);
if (m == NULL)
break;
BPF_MTAP(ifp, m);
len = lance_put(sc, LE_TBUFADDR(sc, bix), m);
#ifdef LEDEBUG
if (len > ETHERMTU + ETHER_HDR_LEN)
if_printf(ifp, "packet length %d\n", len);
#endif
tmd.tmd1 = LE_HTOLE32(LE_T1_OWN | LE_T1_STP | LE_T1_ENP |
LE_T1_ONES | (-len & 0xfff));
tmd.tmd2 = 0;
tmd.tmd3 = 0;
(*sc->sc_copytodesc)(sc, &tmd, rp, sizeof(tmd));
#ifdef LEDEBUG
if (sc->sc_flags & LE_DEBUG)
am79900_xmit_print(sc, bix);
#endif
(*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_INEA | LE_C0_TDMD);
enq++;
if (++bix == sc->sc_ntbuf)
bix = 0;
if (++sc->sc_no_td == sc->sc_ntbuf) {
ifq_set_oactive(&ifp->if_snd);
break;
}
}
sc->sc_last_td = bix;
if (enq > 0)
ifp->if_timer = 5;
}
#ifdef LEDEBUG
static void
am79900_recv_print(struct lance_softc *sc, int no)
{
struct ifnet *ifp = sc->ifp;
struct ether_header eh;
struct lermd rmd;
uint16_t len;
(*sc->sc_copyfromdesc)(sc, &rmd, LE_RMDADDR(sc, no), sizeof(rmd));
len = LE_LE32TOH(rmd.rmd2) & 0xfff;
if_printf(ifp, "receive buffer %d, len = %d\n", no, len);
if_printf(ifp, "status %04x\n", (*sc->sc_rdcsr)(sc, LE_CSR0));
if_printf(ifp, "adr %08x, flags/blen %08x\n", LE_LE32TOH(rmd.rmd0),
LE_LE32TOH(rmd.rmd1));
if (len - ETHER_CRC_LEN >= sizeof(eh)) {
(*sc->sc_copyfrombuf)(sc, &eh, LE_RBUFADDR(sc, no), sizeof(eh));
if_printf(ifp, "dst %s", ether_sprintf(eh.ether_dhost));
kprintf(" src %s type %04x\n", ether_sprintf(eh.ether_shost),
ntohs(eh.ether_type));
}
}
static void
am79900_xmit_print(struct lance_softc *sc, int no)
{
struct ifnet *ifp = sc->ifp;
struct ether_header eh;
struct letmd tmd;
uint16_t len;
(*sc->sc_copyfromdesc)(sc, &tmd, LE_TMDADDR(sc, no), sizeof(tmd));
len = -(LE_LE32TOH(tmd.tmd1) & 0xfff);
if_printf(ifp, "transmit buffer %d, len = %d\n", no, len);
if_printf(ifp, "status %04x\n", (*sc->sc_rdcsr)(sc, LE_CSR0));
if_printf(ifp, "adr %08x, flags/blen %08x\n", LE_LE32TOH(tmd.tmd0),
LE_LE32TOH(tmd.tmd1));
if (len >= sizeof(eh)) {
(*sc->sc_copyfrombuf)(sc, &eh, LE_TBUFADDR(sc, no), sizeof(eh));
if_printf(ifp, "dst %s", ether_sprintf(eh.ether_dhost));
kprintf(" src %s type %04x\n", ether_sprintf(eh.ether_shost),
ntohs(eh.ether_type));
}
}
#endif