[BACK]Return to hme.c CVS log [TXT][DIR] Up to [local] / sys / arch / sparc / dev

File: [local] / sys / arch / sparc / dev / hme.c (download)

Revision 1.1, Tue Mar 4 16:08:04 2008 UTC (16 years, 2 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: hme.c,v 1.55 2006/06/25 21:53:44 brad Exp $	*/

/*
 * Copyright (c) 1998 Jason L. Wright (jason@thought.net)
 * All rights reserved.
 *
 * 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 AUTHOR ``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 AUTHOR 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.
 */

/*
 * Driver for the Happy Meal (hme) ethernet boards
 * Based on information gleaned from reading the
 *	S/Linux driver by David Miller
 *
 * Thanks go to the University of North Carolina at Greensboro, Systems
 * and Networks Department for some of the resources used to develop
 * this driver.
 */

#include "bpfilter.h"
#include "vlan.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.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/malloc.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/netisr.h>
#include <net/if_media.h>

#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#endif

#if NBPFILTER > 0
#include <net/bpf.h>
#include <net/bpfdesc.h>
#endif

#include <machine/autoconf.h>
#include <sparc/cpu.h>
#include <sparc/sparc/cpuvar.h>
#include <sparc/dev/sbusvar.h>
#include <sparc/dev/dmareg.h>	/* for SBUS_BURST_* */
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <sparc/dev/hmereg.h>
#include <sparc/dev/hmevar.h>

int	hmematch(struct device *, void *, void *);
void	hmeattach(struct device *, struct device *, void *);
void	hmewatchdog(struct ifnet *);
int	hmeintr(void *);
int	hmeioctl(struct ifnet *, u_long, caddr_t);
void	hmereset(struct hme_softc *);
void	hmestart(struct ifnet *);
void	hmestop(struct hme_softc *);
void	hmeinit(struct hme_softc *);
void	hme_meminit(struct hme_softc *);

void	hme_tcvr_bb_writeb(struct hme_softc *, int);
int	hme_tcvr_bb_readb(struct hme_softc *, int);

void	hme_poll_stop(struct hme_softc *sc);

int	hme_rint(struct hme_softc *);
int	hme_tint(struct hme_softc *);
int	hme_mint(struct hme_softc *, u_int32_t);
int	hme_eint(struct hme_softc *, u_int32_t);

/* TCP/UDP checksum offload support */
void	hme_rxcksum(struct mbuf *, u_int32_t);

void	hme_reset_rx(struct hme_softc *);
void	hme_reset_tx(struct hme_softc *);

void	hme_read(struct hme_softc *, int, int, u_int32_t);
int	hme_put(struct hme_softc *, int, struct mbuf *);

/*
 * ifmedia glue
 */
int	hme_mediachange(struct ifnet *);
void	hme_mediastatus(struct ifnet *, struct ifmediareq *);

/*
 * mii glue
 */
int	hme_mii_read(struct device *, int, int);
void	hme_mii_write(struct device *, int, int, int);
void	hme_mii_statchg(struct device *);

void	hme_mcreset(struct hme_softc *);

struct cfattach hme_ca = {
	sizeof (struct hme_softc), hmematch, hmeattach
};

struct cfdriver hme_cd = {
	NULL, "hme", DV_IFNET
};

int
hmematch(parent, vcf, aux)
	struct device *parent;
	void *vcf, *aux;
{
	struct cfdata *cf = vcf;
	struct confargs *ca = aux;
	register struct romaux *ra = &ca->ca_ra;

	if (strcmp(cf->cf_driver->cd_name, ra->ra_name) &&
	    strcmp("SUNW,hme", ra->ra_name) &&
	    strcmp("SUNW,qfe", ra->ra_name)) {
		return (0);
	}
	if (!sbus_testdma((struct sbus_softc *)parent, ca))
		return(0);
	return (1);
}

void
hmeattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct confargs *ca = aux;
	struct hme_softc *sc = (struct hme_softc *)self;
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	int pri;
	struct bootpath *bp;
	/* XXX the following declaration should be elsewhere */
	extern void myetheraddr(u_char *);

	if (ca->ca_ra.ra_nintr != 1) {
		printf(": expected 1 interrupt, got %d\n",
			ca->ca_ra.ra_nintr);
		return;
	}
	pri = ca->ca_ra.ra_intr[0].int_pri;

	/* map registers */
	if (ca->ca_ra.ra_nreg != 5) {
		printf(": expected 5 registers, got %d\n", ca->ca_ra.ra_nreg);
		return;
	}
	sc->sc_gr = mapiodev(&(ca->ca_ra.ra_reg[0]), 0,
			ca->ca_ra.ra_reg[0].rr_len);
	sc->sc_txr = mapiodev(&(ca->ca_ra.ra_reg[1]), 0,
			ca->ca_ra.ra_reg[1].rr_len);
	sc->sc_rxr = mapiodev(&(ca->ca_ra.ra_reg[2]), 0,
			ca->ca_ra.ra_reg[2].rr_len);
	sc->sc_cr = mapiodev(&(ca->ca_ra.ra_reg[3]), 0,
			ca->ca_ra.ra_reg[3].rr_len);
	sc->sc_tcvr = mapiodev(&(ca->ca_ra.ra_reg[4]), 0,
			ca->ca_ra.ra_reg[4].rr_len);

	sc->sc_node = ca->ca_ra.ra_node;

	sc->sc_rev = getpropint(ca->ca_ra.ra_node, "hm-rev", -1);
	if (sc->sc_rev == 0xff)
		sc->sc_rev = 0xa0;
	if (sc->sc_rev == 0x20 || sc->sc_rev == 0x21)
		sc->sc_flags = HME_FLAG_20_21;
	else if (sc->sc_rev != 0xa0)
		sc->sc_flags = HME_FLAG_NOT_A0;

	sc->sc_burst = getpropint(ca->ca_ra.ra_node, "burst-sizes", -1);
	if (sc->sc_burst == -1)
		sc->sc_burst = ((struct sbus_softc *)parent)->sc_burst;

	/* Clamp at parent's burst sizes */
	sc->sc_burst &= ((struct sbus_softc *)parent)->sc_burst;

	hme_meminit(sc);

	sc->sc_ih.ih_fun = hmeintr;
	sc->sc_ih.ih_arg = sc;
	intr_establish(ca->ca_ra.ra_intr[0].int_pri, &sc->sc_ih, IPL_NET,
	    self->dv_xname);

	/*
	 * Get MAC address from card if 'local-mac-address' property exists.
	 * Otherwise, use the machine's builtin MAC.
	 */
	if (getprop(ca->ca_ra.ra_node, "local-mac-address",
	    sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN) <= 0) {
		myetheraddr(sc->sc_arpcom.ac_enaddr);
	}

	printf(" pri %d: address %s rev %d\n", pri,
	    ether_sprintf(sc->sc_arpcom.ac_enaddr), sc->sc_rev);

	sc->sc_mii.mii_ifp = ifp;
	sc->sc_mii.mii_readreg = hme_mii_read;
	sc->sc_mii.mii_writereg = hme_mii_write;
	sc->sc_mii.mii_statchg = hme_mii_statchg;
	ifmedia_init(&sc->sc_mii.mii_media, IFM_IMASK, hme_mediachange,
	    hme_mediastatus);
	mii_phy_probe(self, &sc->sc_mii, 0xffffffff);

	if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) {
		ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER | IFM_NONE,
		    0, NULL);
		ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_NONE);
	}
	else
		ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER | IFM_AUTO);

	bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ);
	ifp->if_softc = sc;
	ifp->if_start = hmestart;
	ifp->if_ioctl = hmeioctl;
	ifp->if_watchdog = hmewatchdog;
	ifp->if_flags =
		IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
	sc->sc_if_flags = ifp->if_flags;
	ifp->if_capabilities = IFCAP_VLAN_MTU;
	IFQ_SET_MAXLEN(&ifp->if_snd, HME_TX_RING_SIZE);
	IFQ_SET_READY(&ifp->if_snd);

	/* Attach the interface. */
	if_attach(ifp);
	ether_ifattach(ifp);

	bp = ca->ca_ra.ra_bp;
	if (bp != NULL && sc->sc_dev.dv_unit == bp->val[1] &&
	    ((strcmp(bp->name, hme_cd.cd_name) == 0) ||
	     (strcmp(bp->name, "qfe") == 0) ||
	     (strcmp(bp->name, "SUNW,hme") == 0)))
		bp->dev = &sc->sc_dev;
}

/*
 * Start output on interface.
 * We make two assumptions here:
 *  1) that the current priority is set to splnet _before_ this code
 *     is called *and* is returned to the appropriate priority after
 *     return
 *  2) that the IFF_OACTIVE flag is checked before this code is called
 *     (i.e. that the output part of the interface is idle)
 */
void
hmestart(ifp)
	struct ifnet *ifp;
{
	struct hme_softc *sc = ifp->if_softc;
	struct mbuf *m;
	int bix, len;

	if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
		return;

	bix = sc->sc_last_td;

	for (;;) {
		IFQ_DEQUEUE(&ifp->if_snd, m);
		if (m == NULL)
			break;
#if NBPFILTER > 0
		/*
		 * If BPF is listening on this interface, let it see the
		 * packet before we commit it to the wire.
		 */
		if (ifp->if_bpf)
			bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif

		/*
		 * Copy the mbuf chain into the transmit buffer.
		 */
		len = hme_put(sc, bix, m);

		/*
		 * Initialize transmit registers and start transmission.
		 */
		sc->sc_desc->hme_txd[bix].tx_flags =
		    HME_TXD_OWN | HME_TXD_SOP | HME_TXD_EOP |
		    (len & HME_TXD_SIZE);
		sc->sc_txr->tx_pnding = TXR_TP_DMAWAKEUP;

		if (++bix == HME_TX_RING_SIZE)
			bix = 0;

		if (++sc->sc_no_td == HME_TX_RING_SIZE) {
			ifp->if_flags |= IFF_OACTIVE;
			break;
		}
	}

	sc->sc_last_td = bix;
}

#define MAX_STOP_TRIES	16

void
hmestop(sc)
	struct hme_softc *sc;
{
	int tries = 0;

	sc->sc_gr->reset = GR_RESET_ALL;
	while (sc->sc_gr->reset && (++tries != MAX_STOP_TRIES))
		DELAY(20);
	if (tries == MAX_STOP_TRIES)
		printf("%s: stop failed\n", sc->sc_dev.dv_xname);
	sc->sc_mii.mii_media_status &= ~IFM_ACTIVE;
}

/*
 * Reset interface.
 */
void
hmereset(sc)
	struct hme_softc *sc;
{
	int s;

	s = splnet();
	hmestop(sc);
	hmeinit(sc);
	splx(s);
}

/*
 * Device timeout/watchdog routine. Entered if the device neglects to generate
 * an interrupt after a transmit has been started on it.
 */
void
hmewatchdog(ifp)
	struct ifnet *ifp;
{
	struct hme_softc *sc = ifp->if_softc;

	log(LOG_ERR, "%s: device timeout\n", sc->sc_dev.dv_xname);
	++sc->sc_arpcom.ac_if.if_oerrors;

	hmereset(sc);
}

int
hmeioctl(ifp, cmd, data)
	struct ifnet *ifp;
	u_long cmd;
	caddr_t data;
{
	struct hme_softc *sc = ifp->if_softc;
	struct ifaddr *ifa = (struct ifaddr *)data;
	struct ifreq *ifr = (struct ifreq *)data;
	int s, error = 0;

	s = splnet();

	if ((error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data)) > 0) {
		splx(s);
		return (error);
	}

	switch (cmd) {
	case SIOCSIFADDR:
		switch (ifa->ifa_addr->sa_family) {
#ifdef INET
		case AF_INET:
			if (ifp->if_flags & IFF_UP)
				hme_mcreset(sc);
			else {
				ifp->if_flags |= IFF_UP;
				hmeinit(sc);
			}
			arp_ifinit(&sc->sc_arpcom, ifa);
			break;
#endif /* INET */
		default:
			ifp->if_flags |= IFF_UP;
			hmeinit(sc);
			break;
		}
		break;

	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == 0 &&
		    (ifp->if_flags & IFF_RUNNING) != 0) {
			/*
			 * If interface is marked down and it is running, then
			 * stop it.
			 */
			hmestop(sc);
			ifp->if_flags &= ~IFF_RUNNING;
		} else if ((ifp->if_flags & IFF_UP) != 0 &&
			   (ifp->if_flags & IFF_RUNNING) == 0) {
			/*
			 * If interface is marked up and it is stopped, then
			 * start it.
			 */
			hmeinit(sc);
		} else {
			/*
			 * If setting debug or promiscuous mode, do not reset
			 * the chip; for everything else, call hmeinit()
			 * which will trigger a reset.
			 */
#define RESETIGN (IFF_CANTCHANGE | IFF_DEBUG)
			if (ifp->if_flags == sc->sc_if_flags)
				break;
			if ((ifp->if_flags & (~RESETIGN))
			    == (sc->sc_if_flags & (~RESETIGN)))
				hme_mcreset(sc);
			else
				hmeinit(sc);
#undef RESETIGN
		}
		break;

	case SIOCADDMULTI:
	case SIOCDELMULTI:
		error = (cmd == SIOCADDMULTI) ?
			ether_addmulti(ifr, &sc->sc_arpcom):
			ether_delmulti(ifr, &sc->sc_arpcom);

		if (error == ENETRESET) {
			/*
			 * Multicast list has changed; set the hardware filter
			 * accordingly.
			 */
			if (ifp->if_flags & IFF_RUNNING)
				hme_mcreset(sc);
			error = 0;
		}
		break;
	case SIOCGIFMEDIA:
	case SIOCSIFMEDIA:
		error = ifmedia_ioctl(ifp, ifr,  &sc->sc_mii.mii_media, cmd);
		break;
	default:
		error = ENOTTY;
	}

	sc->sc_if_flags = ifp->if_flags;
	splx(s);
	return (error);
}

void
hme_meminit(sc)
	struct hme_softc *sc;
{
	struct hme_desc *desc;
	int i;

	if (sc->sc_desc_dva == NULL)
		sc->sc_desc_dva = (struct hme_desc *) dvma_malloc(
		    sizeof(struct hme_desc), &sc->sc_desc, M_NOWAIT);
	if (sc->sc_bufs_dva == NULL)
		sc->sc_bufs_dva = (struct hme_bufs *) dvma_malloc(
		    sizeof(struct hme_bufs), &sc->sc_bufs, M_NOWAIT);

	desc = sc->sc_desc;

	/*
	 * Setup TX descriptors
	 */
	sc->sc_first_td = sc->sc_last_td = sc->sc_no_td = 0;
	for (i = 0; i < HME_TX_RING_SIZE; i++) {
		desc->hme_txd[i].tx_addr =
		    (u_int32_t)sc->sc_bufs_dva->tx_buf[i];
		desc->hme_txd[i].tx_flags = 0;
	}

	/*
	 * Setup RX descriptors
	 */
	sc->sc_last_rd = 0;
	for (i = 0; i < HME_RX_RING_SIZE; i++) {
		desc->hme_rxd[i].rx_addr =
		    (u_int32_t)sc->sc_bufs_dva->rx_buf[i];
		desc->hme_rxd[i].rx_flags = HME_RXD_OWN |
		    ((HME_RX_PKT_BUF_SZ - HME_RX_OFFSET) << 16);
	}
}

void
hmeinit(sc)
	struct hme_softc *sc;
{
	u_int32_t c, n;
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	struct hme_tcvr *tcvr = sc->sc_tcvr;
	struct hme_cr *cr = sc->sc_cr;
	struct hme_gr *gr = sc->sc_gr;
	struct hme_txr *txr = sc->sc_txr;
	struct hme_rxr *rxr = sc->sc_rxr;

	hme_poll_stop(sc);
	hmestop(sc);

	hme_meminit(sc);

	tcvr->int_mask = 0xffff;

	c = tcvr->cfg;
	if (sc->sc_flags & HME_FLAG_FENABLE)
		tcvr->cfg = c & ~(TCVR_CFG_BENABLE);
	else
		tcvr->cfg = c | TCVR_CFG_BENABLE;

	hme_reset_tx(sc);
	hme_reset_rx(sc);

	cr->rand_seed = sc->sc_arpcom.ac_enaddr[5] |
	    ((sc->sc_arpcom.ac_enaddr[4] << 8) & 0x3f00);
	cr->mac_addr0 = (sc->sc_arpcom.ac_enaddr[0] << 8) |
			   sc->sc_arpcom.ac_enaddr[1];
	cr->mac_addr1 = (sc->sc_arpcom.ac_enaddr[2] << 8) |
			   sc->sc_arpcom.ac_enaddr[3];
	cr->mac_addr2 = (sc->sc_arpcom.ac_enaddr[4] << 8) |
			   sc->sc_arpcom.ac_enaddr[5];
	cr->tx_pkt_max = cr->rx_pkt_max = ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN;

	cr->jsize = HME_DEFAULT_JSIZE;
	cr->ipkt_gap1 = HME_DEFAULT_IPKT_GAP1;
	cr->ipkt_gap2 = HME_DEFAULT_IPKT_GAP2;

	rxr->rx_ring = (u_int32_t)sc->sc_desc_dva->hme_rxd;
	txr->tx_ring = (u_int32_t)sc->sc_desc_dva->hme_txd;

	if (sc->sc_burst & SBUS_BURST_64)
		gr->cfg = GR_CFG_BURST64;
	else if (sc->sc_burst & SBUS_BURST_32)
		gr->cfg = GR_CFG_BURST32;
	else if (sc->sc_burst & SBUS_BURST_16)
		gr->cfg = GR_CFG_BURST16;
	else {
		printf("%s: burst size unknown\n", sc->sc_dev.dv_xname);
		gr->cfg = 0;
	}

	gr->imask = GR_IMASK_SENTFRAME | GR_IMASK_TXPERR |
	              GR_IMASK_GOTFRAME | GR_IMASK_RCNTEXP;

	txr->tx_rsize = (HME_TX_RING_SIZE >> TXR_RSIZE_SHIFT) - 1;
	txr->cfg |= TXR_CFG_DMAENABLE;

	c = RXR_CFG_DMAENABLE | (HME_RX_OFFSET << 3);
	/* RX TCP/UDP cksum offset */
	n = (ETHER_HDR_LEN + sizeof(struct ip)) / 2;
	n = (n << RXR_CFG_CSUM_SHIFT) & RXR_CFG_CSUMSTART;
	c |= n;
#if HME_RX_RING_SIZE == 32
	c |= RXR_CFG_RINGSIZE32;
#elif HME_RX_RING_SIZE == 64
	c |= RXR_CFG_RINGSIZE64;
#elif HME_RX_RING_SIZE == 128
	c |= RXR_CFG_RINGSIZE128;
#elif HME_RX_RING_SIZE == 256
	c |= RXR_CFG_RINGSIZE256;
#else
#error "HME_RX_RING_SIZE must be 32, 64, 128, or 256."
#endif
	rxr->cfg = c;
	DELAY(20);
	if (c != rxr->cfg)	/* the receiver sometimes misses bits */
		printf("%s: setting rxreg->cfg failed.\n", sc->sc_dev.dv_xname);

	cr->rx_cfg = 0;
	hme_mcreset(sc);
	DELAY(10);

	cr->tx_cfg |= CR_TXCFG_DGIVEUP;

	c = CR_XCFG_ODENABLE;
	if (sc->sc_flags & HME_FLAG_LANCE)
		c |= (HME_DEFAULT_IPKT_GAP0 << 5) | CR_XCFG_LANCE;
	cr->xif_cfg = c;

	cr->tx_cfg |= CR_TXCFG_ENABLE;	/* enable tx */
	cr->rx_cfg |= CR_RXCFG_ENABLE;	/* enable rx */

	mii_mediachg(&sc->sc_mii);

	ifp->if_flags |= IFF_RUNNING;
	ifp->if_flags &= ~IFF_OACTIVE;
	sc->sc_if_flags = ifp->if_flags;
	ifp->if_timer = 0;
}

void
hme_poll_stop(sc)
	struct hme_softc *sc;
{
	struct hme_tcvr *tcvr = sc->sc_tcvr;

	/* if not polling, or polling not enabled, we're done. */
	if ((sc->sc_flags & (HME_FLAG_POLLENABLE | HME_FLAG_POLL)) != 
	    (HME_FLAG_POLLENABLE | HME_FLAG_POLL))
		return;

	/* Turn off MIF interrupts, and disable polling */
	tcvr->int_mask = 0xffff;
	tcvr->cfg &= ~(TCVR_CFG_PENABLE);
	sc->sc_flags &= ~(HME_FLAG_POLL);
	DELAY(200);
}

#define RESET_TRIES	32

void
hme_reset_tx(sc)
	struct hme_softc *sc;
{
	int tries = RESET_TRIES;
	struct hme_cr *cr = sc->sc_cr;

	cr->tx_swreset = 0;
	while (--tries && (cr->tx_swreset & 1))
		DELAY(20);

	if (!tries)
		printf("%s: reset tx failed\n", sc->sc_dev.dv_xname);
}

void
hme_reset_rx(sc)
	struct hme_softc *sc;
{
	int tries = RESET_TRIES;
	struct hme_cr *cr = sc->sc_cr;

	cr->rx_swreset = 0;
	while (--tries && (cr->rx_swreset & 1))
		DELAY(20);

	if (!tries)
		printf("%s: reset rx failed\n", sc->sc_dev.dv_xname);
}

/*
 * mif interrupt
 */
int
hme_mint(sc, why)
	struct hme_softc *sc;
	u_int32_t why;
{
	printf("%s: link status changed\n", sc->sc_dev.dv_xname);
	hme_poll_stop(sc);
	return (1);
}

/*
 * transmit interrupt
 */
int
hme_tint(sc)
	struct hme_softc *sc;
{
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	struct hme_cr *cr = sc->sc_cr;
	int bix;
	struct hme_txd txd;

	/*
	 * Get collision counters
	 */
	ifp->if_collisions += cr->ex_ctr + cr->lt_ctr + cr->fc_ctr + cr->nc_ctr;
	cr->ex_ctr = 0;
	cr->lt_ctr = 0;
	cr->fc_ctr = 0;
	cr->nc_ctr = 0;

	bix = sc->sc_first_td;

	for (;;) {
		if (sc->sc_no_td <= 0)
			break;

		bcopy(&sc->sc_desc->hme_txd[bix], &txd, sizeof(txd));

		if (txd.tx_flags & HME_TXD_OWN)
			break;

		ifp->if_flags &= ~IFF_OACTIVE;
		ifp->if_opackets++;

		if (++bix == HME_TX_RING_SIZE)
			bix = 0;

		--sc->sc_no_td;
	}

	sc->sc_first_td = bix;

	hmestart(ifp);

	if (sc->sc_no_td == 0)
		ifp->if_timer = 0;

	return (1);
}

/*
 * XXX layering violation
 *
 * If we can have additional csum data member in 'struct pkthdr' for
 * these incomplete checksum offload capable hardware, things would be
 * much simpler. That member variable will carry partial checksum
 * data and it may be evaluated in TCP/UDP input handler after
 * computing pseudo header checksumming.
 */
void
hme_rxcksum(struct mbuf *m, u_int32_t flags)
{
	struct ether_header *eh;
	struct ip *ip;
	struct udphdr *uh;
	int32_t hlen, len, pktlen;
	u_int16_t cksum, *opts;
	u_int32_t temp32;
	union pseudoh {
		struct hdr {
			u_int16_t len;
			u_int8_t ttl;
			u_int8_t proto;
			u_int32_t src;
			u_int32_t dst;
		} h;
		u_int16_t w[6];
	} ph;

	pktlen = m->m_pkthdr.len;
	if (pktlen < sizeof(struct ether_header))
		return;
	eh = mtod(m, struct ether_header *);
	if (eh->ether_type != htons(ETHERTYPE_IP))
		return;
	ip = (struct ip *)(eh + 1);
	if (ip->ip_v != IPVERSION)
		return;

	hlen = ip->ip_hl << 2;
	pktlen -= sizeof(struct ether_header);
	if (hlen < sizeof(struct ip))
		return;
	if (ntohs(ip->ip_len) < hlen)
		return;
	if (ntohs(ip->ip_len) != pktlen) 
		return;
	if (ip->ip_off & htons(IP_MF | IP_OFFMASK))
		return;	/* can't handle fragmented packet */

	switch (ip->ip_p) {
	case IPPROTO_TCP:
		if (pktlen < (hlen + sizeof(struct tcphdr)))
			return;
		break;
	case IPPROTO_UDP:
		if (pktlen < (hlen + sizeof(struct udphdr)))
			return;
		uh = (struct udphdr *)((caddr_t)ip + hlen);
		if (uh->uh_sum == 0)
			return; /* no checksum */
		break;
	default:
		return;
	}

	cksum = htons(~(flags & HME_RXD_CSUM));
	/* cksum fixup for IP options */
	len = hlen - sizeof(struct ip);
	if (len > 0) {
		opts = (u_int16_t *)(ip + 1);
		for (; len > 0; len -= sizeof(u_int16_t), opts++) {
			temp32 = cksum - *opts;
			temp32 = (temp32 >> 16) + (temp32 & 65535);
			cksum = temp32 & 65535;
		}
	}
	/* cksum fixup for pseudo-header, replace with in_cksum_phdr()? */
	ph.h.len = htons(ntohs(ip->ip_len) - hlen);
	ph.h.ttl = 0;
	ph.h.proto = ip->ip_p;
	ph.h.src = ip->ip_src.s_addr;
	ph.h.dst = ip->ip_dst.s_addr;
	temp32 = cksum;
	opts = &ph.w[0];
	temp32 += opts[0] + opts[1] + opts[2] + opts[3] + opts[4] + opts[5];
	temp32 = (temp32 >> 16) + (temp32 & 65535);
	temp32 += (temp32 >> 16);
	cksum = ~temp32;
	if (cksum == 0) {
		m->m_pkthdr.csum_flags |=
			M_TCP_CSUM_IN_OK | M_UDP_CSUM_IN_OK;
	}
}

int
hme_rint(sc)
	struct hme_softc *sc;
{
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	int bix, len;
	struct hme_rxd rxd;

	bix = sc->sc_last_rd;

	for (;;) {
		bcopy(&sc->sc_desc->hme_rxd[bix], &rxd, sizeof(rxd));
		len = rxd.rx_flags >> 16;

		if (rxd.rx_flags & HME_RXD_OWN)
			break;

		if (rxd.rx_flags & HME_RXD_OVERFLOW)
			ifp->if_ierrors++;
		else
			hme_read(sc, bix, len, rxd.rx_flags);

		rxd.rx_flags = HME_RXD_OWN |
		    ((HME_RX_PKT_BUF_SZ - HME_RX_OFFSET) << 16);
		bcopy(&rxd, &sc->sc_desc->hme_rxd[bix], sizeof(rxd));

		if (++bix == HME_RX_RING_SIZE)
			bix = 0;
	}

	sc->sc_last_rd = bix;

	return (1);
}

/*
 * error interrupt
 */
int
hme_eint(sc, why)
	struct hme_softc *sc;
	u_int32_t why;
{
	if (why & GR_STAT_NORXD) {
		sc->sc_arpcom.ac_if.if_ierrors++;
		why &= ~GR_STAT_NORXD;
	}
	if (why & GR_STAT_DTIMEXP) {
		sc->sc_arpcom.ac_if.if_oerrors++;
		why &= ~GR_STAT_DTIMEXP;
	}

	if (why & GR_STAT_ALL_ERRORS) {
		printf("%s: stat=%b, resetting.\n", sc->sc_dev.dv_xname,
		    why, GR_STAT_BITS);
		hmereset(sc);
	}

	return (1);
}

/*
 * Interrupt handler
 */
int
hmeintr(v)
	void *v;
{
	struct hme_softc *sc = (struct hme_softc *)v;
	struct hme_gr *gr = sc->sc_gr;
	u_int32_t why;
	int r = 0;

	why = gr->stat;

	if (why & GR_STAT_ALL_ERRORS)
		r |= hme_eint(sc, why);

	if (why & GR_STAT_MIFIRQ)
		r |= hme_mint(sc, why);

	if (why & (GR_STAT_TXALL | GR_STAT_HOSTTOTX))
		r |= hme_tint(sc);

	if (why & GR_STAT_RXTOHOST)
		r |= hme_rint(sc);

	return (r);
}

int
hme_put(sc, idx, m)
	struct hme_softc *sc;
	int idx;
	struct mbuf *m;
{
	struct mbuf *n;
	u_int8_t *buf = sc->sc_bufs->tx_buf[idx];
	int len, tlen = 0;

	for (; m; m = n) {
		len = m->m_len;
		if (len == 0) {
			MFREE(m, n);
			continue;
		}
		bcopy(mtod(m, caddr_t), buf, len);
		buf += len;
		tlen += len;
		MFREE(m, n);
	}
	return (tlen);
}

void
hme_read(sc, idx, len, flags)
	struct hme_softc *sc;
	int idx, len;
	u_int32_t flags;
{
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	struct mbuf *m;

	if (len <= sizeof(struct ether_header) ||
	    len > ETHERMTU + sizeof(struct ether_header)) {
		printf("%s: invalid packet size %d; dropping\n",
		    ifp->if_xname, len);
		ifp->if_ierrors++;
		return;
	}

	/* Pull packet off interface. */
	m = m_devget(sc->sc_bufs->rx_buf[idx], len + HME_RX_OFFSET, 0,
	    &sc->sc_arpcom.ac_if, NULL);
	if (m == NULL) {
		ifp->if_ierrors++;
		return;
	}
	m_adj(m, HME_RX_OFFSET);

	ifp->if_ipackets++;
	hme_rxcksum(m, flags);

#if NBPFILTER > 0
	/*
	 * Check if there's a BPF listener on this interface.
	 * If so, hand off the raw packet to BPF.
	 */
	if (ifp->if_bpf)
		bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN);
#endif
	/* Pass the packet up. */
	ether_input_mbuf(ifp, m);
}

/*
 * Program the multicast receive filter.
 */
void
hme_mcreset(sc)
	struct hme_softc *sc;
{
	struct arpcom *ac = &sc->sc_arpcom;
	struct ifnet *ifp = &sc->sc_arpcom.ac_if;
	struct hme_cr *cr = sc->sc_cr;
	u_int32_t crc;
	u_int16_t hash[4];
	u_int8_t octet;
	int i, j;
	struct ether_multi *enm;
	struct ether_multistep step;

	if (ifp->if_flags & IFF_PROMISC) {
		cr->rx_cfg |= CR_RXCFG_PMISC;
		return;
	}
	else
		cr->rx_cfg &= ~CR_RXCFG_PMISC;

	if (ifp->if_flags & IFF_ALLMULTI) {
		cr->htable3 = 0xffff;
		cr->htable2 = 0xffff;
		cr->htable1 = 0xffff;
		cr->htable0 = 0xffff;
		cr->rx_cfg |= CR_RXCFG_HENABLE;
		return;
	}

	hash[3] = hash[2] = hash[1] = hash[0] = 0;

	ETHER_FIRST_MULTI(step, ac, enm);
	while (enm != NULL) {
		if (bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
			/*
			 * We must listen to a range of multicast
			 * addresses.  For now, just accept all
			 * multicasts, rather than trying to set only
			 * those filter bits needed to match the range.
			 * (At this time, the only use of address
			 * ranges is for IP multicast routing, for
			 * which the range is big enough to require
			 * all bits set.)
			 */
			cr->htable3 = 0xffff;
			cr->htable2 = 0xffff;
			cr->htable1 = 0xffff;
			cr->htable0 = 0xffff;
			cr->rx_cfg |= CR_RXCFG_HENABLE;
			ifp->if_flags |= IFF_ALLMULTI;
			return;
		}

		crc = 0xffffffff;

		for (i = 0; i < ETHER_ADDR_LEN; i++) {
			octet = enm->enm_addrlo[i];

			for (j = 0; j < 8; j++) {
				if ((crc & 1) ^ (octet & 1)) {
					crc >>= 1;
					crc ^= ETHER_CRC_POLY_LE;
				}
				else
					crc >>= 1;
				octet >>= 1;
			}
		}

		crc >>=26;
		hash[crc >> 4] |= 1 << (crc & 0xf);
		ETHER_NEXT_MULTI(step, enm);
	}
	cr->htable3 = hash[3];
	cr->htable2 = hash[2];
	cr->htable1 = hash[1];
	cr->htable0 = hash[0];
	cr->rx_cfg |= CR_RXCFG_HENABLE;
	ifp->if_flags &= ~IFF_ALLMULTI;
}

/*
 * Writing to the serial BitBang, is a matter of putting the bit
 * into the data register, then strobing the clock.
 */
void
hme_tcvr_bb_writeb(sc, b)
	struct hme_softc *sc;
	int b;
{
	sc->sc_tcvr->bb_data = b & 0x1;
	sc->sc_tcvr->bb_clock = 0;
	sc->sc_tcvr->bb_clock = 1;
}

/*
 * Read a bit from a PHY, if the PHY is not our internal or external
 * phy addr, just return all zero's.
 */
int
hme_tcvr_bb_readb(sc, phy)
	struct hme_softc *sc;
	int phy;
{
	int ret;

	sc->sc_tcvr->bb_clock = 0;
	DELAY(10);

	if (phy == TCVR_PHYADDR_ITX)
		ret = sc->sc_tcvr->cfg & TCVR_CFG_MDIO0;
	else if (phy == TCVR_PHYADDR_ETX)
		ret = sc->sc_tcvr->cfg & TCVR_CFG_MDIO1;
	else
		ret = 0;

	sc->sc_tcvr->bb_clock = 1;

	return ((ret) ? 1 : 0);
}

void
hme_mii_write(self, phy, reg, val)
	struct device *self;
	int phy, reg, val;
{
	struct hme_softc *sc = (struct hme_softc *)self;
	struct hme_tcvr *tcvr = sc->sc_tcvr;
	int tries = 16, i;

	if (sc->sc_flags & HME_FLAG_FENABLE) {
		tcvr->frame = (FRAME_WRITE | phy << 23) |
		    ((reg & 0xff) << 18) | (val & 0xffff);
		while (!(tcvr->frame & 0x10000) && (tries != 0)) {
			tries--;
			DELAY(200);
		}
		if (!tries)
			printf("%s: mii_write failed\n", sc->sc_dev.dv_xname);
		return;
	}

	tcvr->bb_oenab = 1;

	for (i = 0; i < 32; i++)
		hme_tcvr_bb_writeb(sc, 1);

	hme_tcvr_bb_writeb(sc, (MII_COMMAND_START >> 1) & 1);
	hme_tcvr_bb_writeb(sc, MII_COMMAND_START & 1);
	hme_tcvr_bb_writeb(sc, (MII_COMMAND_WRITE >> 1) & 1);
	hme_tcvr_bb_writeb(sc, MII_COMMAND_WRITE & 1);

	for (i = 4; i >= 0; i--)
		hme_tcvr_bb_writeb(sc, (phy >> i) & 1);

	for (i = 4; i >= 0; i--)
		hme_tcvr_bb_writeb(sc, (reg >> i) & 1);

	for (i = 15; i >= 0; i--)
		hme_tcvr_bb_writeb(sc, (reg >> i) & 1);

	tcvr->bb_oenab = 0;
}

int
hme_mii_read(self, phy, reg)
	struct device *self;
	int phy, reg;
{
	struct hme_softc *sc = (struct hme_softc *)self;
	struct hme_tcvr *tcvr = sc->sc_tcvr;
	int tries = 16, i, ret = 0;

	/* Use the frame if possible */
	if (sc->sc_flags & HME_FLAG_FENABLE) {
		tcvr->frame = (FRAME_READ | phy << 23) |
		    ((reg & 0xff) << 18);
		while (!(tcvr->frame & 0x10000) && (tries != 0)) {
			tries--;
			DELAY(20);
		}
		if (!tries) {
			printf("%s: mii_read failed\n", sc->sc_dev.dv_xname);
			return (0);
		}
		return (tcvr->frame & 0xffff);
	}

	tcvr->bb_oenab = 1;

	for (i = 0; i < 32; i++)		/* make bitbang idle */
		hme_tcvr_bb_writeb(sc, 1);

	hme_tcvr_bb_writeb(sc, (MII_COMMAND_START >> 1) & 1);
	hme_tcvr_bb_writeb(sc, MII_COMMAND_START & 1);
	hme_tcvr_bb_writeb(sc, (MII_COMMAND_READ >> 1) & 1);
	hme_tcvr_bb_writeb(sc, MII_COMMAND_READ & 1);

	for (i = 4; i >= 0; i--)
		hme_tcvr_bb_writeb(sc, (phy >> i) & 1);

	for (i = 4; i >= 0; i--)
		hme_tcvr_bb_writeb(sc, (reg >> i) & 1);

	tcvr->bb_oenab = 0;			/* turn off bitbang intrs */

	hme_tcvr_bb_readb(sc, phy);		/* ignore... */

	for (i = 15; i >= 15; i--)		/* read value */
		ret |= hme_tcvr_bb_readb(sc, phy) << i;

	hme_tcvr_bb_readb(sc, phy);			/* ignore... */
	hme_tcvr_bb_readb(sc, phy);			/* ignore... */
	hme_tcvr_bb_readb(sc, phy);			/* ignore... */

	return (ret);
}

int
hme_mediachange(ifp)
	struct ifnet *ifp;
{
	if (ifp->if_flags & IFF_UP)
		hmeinit(ifp->if_softc);
	return (0);
}

void
hme_mediastatus(ifp, ifmr)
	struct ifnet *ifp;
	struct ifmediareq *ifmr;
{
	struct hme_softc *sc = (struct hme_softc *)ifp->if_softc;

	mii_pollstat(&sc->sc_mii);
	ifmr->ifm_active = sc->sc_mii.mii_media_active;
	ifmr->ifm_status = sc->sc_mii.mii_media_status;
}

void
hme_mii_statchg(self)
	struct device *self;
{
	struct hme_softc *sc = (struct hme_softc *)self;
	struct hme_cr *cr = sc->sc_cr;

	/* Apparently the hme chip is SIMPLEX if working in full duplex mode,
	   but not otherwise. */
	if ((IFM_OPTIONS(sc->sc_mii.mii_media_active) & IFM_FDX) != 0) {
		cr->tx_cfg |= CR_TXCFG_FULLDPLX;
		sc->sc_arpcom.ac_if.if_flags |= IFF_SIMPLEX;
	} else {
		cr->tx_cfg &= ~CR_TXCFG_FULLDPLX;
		sc->sc_arpcom.ac_if.if_flags &= ~IFF_SIMPLEX;
	}
	sc->sc_if_flags = sc->sc_arpcom.ac_if.if_flags;
}