/*-
 * Copyright (c) 1996, Javier Martín Rueda (jmrueda@diatel.upm.es)
 * 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 unmodified, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 *
 * MAINTAINER: Matthew N. Dodd <winter@jurai.net>
 *                             <mdodd@FreeBSD.org>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.0.0/sys/dev/ex/if_ex.c 207554 2010-05-03 07:32:50Z sobomax $");

/*
 * Intel EtherExpress Pro/10, Pro/10+ Ethernet driver
 *
 * Revision history:
 *
 * dd-mmm-yyyy: Multicast support ported from NetBSD's if_iy driver.
 * 30-Oct-1996: first beta version. Inet and BPF supported, but no multicast.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>

#include <sys/module.h>
#include <sys/bus.h>

#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>

#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h> 
#include <net/if_types.h> 
#include <net/ethernet.h>
#include <net/bpf.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>


#include <isa/isavar.h>
#include <isa/pnpvar.h>

#include <dev/ex/if_exreg.h>
#include <dev/ex/if_exvar.h>

#ifdef EXDEBUG
# define Start_End 1
# define Rcvd_Pkts 2
# define Sent_Pkts 4
# define Status    8
static int debug_mask = 0;
# define DODEBUG(level, action) if (level & debug_mask) action
#else
# define DODEBUG(level, action)
#endif

devclass_t ex_devclass;

char irq2eemap[] =
	{ -1, -1, 0, 1, -1, 2, -1, -1, -1, 0, 3, 4, -1, -1, -1, -1 };
u_char ee2irqmap[] =
	{ 9, 3, 5, 10, 11, 0, 0, 0 };
                
char plus_irq2eemap[] =
	{ -1, -1, -1, 0, 1, 2, -1, 3, -1, 4, 5, 6, 7, -1, -1, -1 };
u_char plus_ee2irqmap[] =
	{ 3, 4, 5, 7, 9, 10, 11, 12 };

/* Network Interface Functions */
static void	ex_init(void *);
static void	ex_init_locked(struct ex_softc *);
static void	ex_start(struct ifnet *);
static void	ex_start_locked(struct ifnet *);
static int	ex_ioctl(struct ifnet *, u_long, caddr_t);
static void	ex_watchdog(void *);

/* ifmedia Functions	*/
static int	ex_ifmedia_upd(struct ifnet *);
static void	ex_ifmedia_sts(struct ifnet *, struct ifmediareq *);

static int	ex_get_media(struct ex_softc *);

static void	ex_reset(struct ex_softc *);
static void	ex_setmulti(struct ex_softc *);

static void	ex_tx_intr(struct ex_softc *);
static void	ex_rx_intr(struct ex_softc *);

void
ex_get_address(struct ex_softc *sc, u_char *enaddr)
{
	uint16_t	eaddr_tmp;

	eaddr_tmp = ex_eeprom_read(sc, EE_Eth_Addr_Lo);
	enaddr[5] = eaddr_tmp & 0xff;
	enaddr[4] = eaddr_tmp >> 8;
	eaddr_tmp = ex_eeprom_read(sc, EE_Eth_Addr_Mid);
	enaddr[3] = eaddr_tmp & 0xff;
	enaddr[2] = eaddr_tmp >> 8;
	eaddr_tmp = ex_eeprom_read(sc, EE_Eth_Addr_Hi);
	enaddr[1] = eaddr_tmp & 0xff;
	enaddr[0] = eaddr_tmp >> 8;
	
	return;
}

int
ex_card_type(u_char *enaddr)
{
	if ((enaddr[0] == 0x00) && (enaddr[1] == 0xA0) && (enaddr[2] == 0xC9))
		return (CARD_TYPE_EX_10_PLUS);

	return (CARD_TYPE_EX_10);
}

/*
 * Caller is responsible for eventually calling
 * ex_release_resources() on failure.
 */
int
ex_alloc_resources(device_t dev)
{
	struct ex_softc *	sc = device_get_softc(dev);
	int			error = 0;

	sc->ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
					    &sc->ioport_rid, RF_ACTIVE);
	if (!sc->ioport) {
		device_printf(dev, "No I/O space?!\n");
		error = ENOMEM;
		goto bad;
	}

	sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
					RF_ACTIVE);

	if (!sc->irq) {
		device_printf(dev, "No IRQ?!\n");
		error = ENOMEM;
		goto bad;
	}

bad:
	return (error);
}

void
ex_release_resources(device_t dev)
{
	struct ex_softc *	sc = device_get_softc(dev);

	if (sc->ih) {
		bus_teardown_intr(dev, sc->irq, sc->ih);
		sc->ih = NULL;
	}

	if (sc->ioport) {
		bus_release_resource(dev, SYS_RES_IOPORT,
					sc->ioport_rid, sc->ioport);
		sc->ioport = NULL;
	}

	if (sc->irq) {
		bus_release_resource(dev, SYS_RES_IRQ,
					sc->irq_rid, sc->irq);
		sc->irq = NULL;
	}

	if (sc->ifp)
		if_free(sc->ifp);

	return;
}

int
ex_attach(device_t dev)
{
	struct ex_softc *	sc = device_get_softc(dev);
	struct ifnet *		ifp;
	struct ifmedia *	ifm;
	int			error;
	uint16_t		temp;

	ifp = sc->ifp = if_alloc(IFT_ETHER);
	if (ifp == NULL) {
		device_printf(dev, "can not if_alloc()\n");
		return (ENOSPC);
	}
	/* work out which set of irq <-> internal tables to use */
	if (ex_card_type(sc->enaddr) == CARD_TYPE_EX_10_PLUS) {
		sc->irq2ee = plus_irq2eemap;
		sc->ee2irq = plus_ee2irqmap;
	} else {
		sc->irq2ee = irq2eemap;
		sc->ee2irq = ee2irqmap;
	}

	sc->mem_size = CARD_RAM_SIZE;	/* XXX This should be read from the card itself. */

	/*
	 * Initialize the ifnet structure.
	 */
	ifp->if_softc = sc;
	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
	ifp->if_mtu = ETHERMTU;
	ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST;
	ifp->if_start = ex_start;
	ifp->if_ioctl = ex_ioctl;
	ifp->if_init = ex_init;
	IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);

	ifmedia_init(&sc->ifmedia, 0, ex_ifmedia_upd, ex_ifmedia_sts);
	mtx_init(&sc->lock, device_get_nameunit(dev), MTX_NETWORK_LOCK,
	    MTX_DEF);
	callout_init_mtx(&sc->timer, &sc->lock, 0);

	temp = ex_eeprom_read(sc, EE_W5);
	if (temp & EE_W5_PORT_TPE)
		ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_T, 0, NULL);
	if (temp & EE_W5_PORT_BNC)
		ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_2, 0, NULL);
	if (temp & EE_W5_PORT_AUI)
		ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_5, 0, NULL);

	ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_AUTO, 0, NULL);
	ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_NONE, 0, NULL);
	ifmedia_set(&sc->ifmedia, ex_get_media(sc));

	ifm = &sc->ifmedia;
	ifm->ifm_media = ifm->ifm_cur->ifm_media;	
	ex_ifmedia_upd(ifp);

	/*
	 * Attach the interface.
	 */
	ether_ifattach(ifp, sc->enaddr);

	error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE,
				NULL, ex_intr, (void *)sc, &sc->ih);
	if (error) {
		device_printf(dev, "bus_setup_intr() failed!\n");
		ether_ifdetach(ifp);
		mtx_destroy(&sc->lock);
		return (error);
	}

	return(0);
}

int
ex_detach(device_t dev)
{
	struct ex_softc	*sc;
	struct ifnet	*ifp;

	sc = device_get_softc(dev);
	ifp = sc->ifp;

	EX_LOCK(sc);
        ex_stop(sc);
	EX_UNLOCK(sc);

	ether_ifdetach(ifp);
	callout_drain(&sc->timer);

	ex_release_resources(dev);
	mtx_destroy(&sc->lock);

	return (0);
}

static void
ex_init(void *xsc)
{
	struct ex_softc *	sc = (struct ex_softc *) xsc;

	EX_LOCK(sc);
	ex_init_locked(sc);
	EX_UNLOCK(sc);
}

static void
ex_init_locked(struct ex_softc *sc)
{
	struct ifnet *		ifp = sc->ifp;
	int			i;
	unsigned short		temp_reg;

	DODEBUG(Start_End, printf("%s: ex_init: start\n", ifp->if_xname););

	sc->tx_timeout = 0;

	/*
	 * Load the ethernet address into the card.
	 */
	CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
	temp_reg = CSR_READ_1(sc, EEPROM_REG);
	if (temp_reg & Trnoff_Enable)
		CSR_WRITE_1(sc, EEPROM_REG, temp_reg & ~Trnoff_Enable);
	for (i = 0; i < ETHER_ADDR_LEN; i++)
		CSR_WRITE_1(sc, I_ADDR_REG0 + i, IF_LLADDR(sc->ifp)[i]);

	/*
	 * - Setup transmit chaining and discard bad received frames.
	 * - Match broadcast.
	 * - Clear test mode.
	 * - Set receiving mode.
	 */
	CSR_WRITE_1(sc, REG1, CSR_READ_1(sc, REG1) | Tx_Chn_Int_Md | Tx_Chn_ErStp | Disc_Bad_Fr);
	CSR_WRITE_1(sc, REG2, CSR_READ_1(sc, REG2) | No_SA_Ins | RX_CRC_InMem);
	CSR_WRITE_1(sc, REG3, CSR_READ_1(sc, REG3) & 0x3f /* XXX constants. */ );
	/*
	 * - Set IRQ number, if this part has it.  ISA devices have this,
	 * while PC Card devices don't seem to.  Either way, we have to
	 * switch to Bank1 as the rest of this code relies on that.
	 */
	CSR_WRITE_1(sc, CMD_REG, Bank1_Sel);
	if (sc->flags & HAS_INT_NO_REG)
		CSR_WRITE_1(sc, INT_NO_REG,
		    (CSR_READ_1(sc, INT_NO_REG) & 0xf8) |
		    sc->irq2ee[sc->irq_no]);

	/*
	 * Divide the available memory in the card into rcv and xmt buffers.
	 * By default, I use the first 3/4 of the memory for the rcv buffer,
	 * and the remaining 1/4 of the memory for the xmt buffer.
	 */
	sc->rx_mem_size = sc->mem_size * 3 / 4;
	sc->tx_mem_size = sc->mem_size - sc->rx_mem_size;
	sc->rx_lower_limit = 0x0000;
	sc->rx_upper_limit = sc->rx_mem_size - 2;
	sc->tx_lower_limit = sc->rx_mem_size;
	sc->tx_upper_limit = sc->mem_size - 2;
	CSR_WRITE_1(sc, RCV_LOWER_LIMIT_REG, sc->rx_lower_limit >> 8);
	CSR_WRITE_1(sc, RCV_UPPER_LIMIT_REG, sc->rx_upper_limit >> 8);
	CSR_WRITE_1(sc, XMT_LOWER_LIMIT_REG, sc->tx_lower_limit >> 8);
	CSR_WRITE_1(sc, XMT_UPPER_LIMIT_REG, sc->tx_upper_limit >> 8);
	
	/*
	 * Enable receive and transmit interrupts, and clear any pending int.
	 */
	CSR_WRITE_1(sc, REG1, CSR_READ_1(sc, REG1) | TriST_INT);
	CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);
	CSR_WRITE_1(sc, MASK_REG, All_Int & ~(Rx_Int | Tx_Int));
	CSR_WRITE_1(sc, STATUS_REG, All_Int);

	/*
	 * Initialize receive and transmit ring buffers.
	 */
	CSR_WRITE_2(sc, RCV_BAR, sc->rx_lower_limit);
	sc->rx_head = sc->rx_lower_limit;
	CSR_WRITE_2(sc, RCV_STOP_REG, sc->rx_upper_limit | 0xfe);
	CSR_WRITE_2(sc, XMT_BAR, sc->tx_lower_limit);
	sc->tx_head = sc->tx_tail = sc->tx_lower_limit;

	ifp->if_drv_flags |= IFF_DRV_RUNNING;
	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
	DODEBUG(Status, printf("OIDLE init\n"););
	callout_reset(&sc->timer, hz, ex_watchdog, sc);
	
	ex_setmulti(sc);
	
	/*
	 * Final reset of the board, and enable operation.
	 */
	CSR_WRITE_1(sc, CMD_REG, Sel_Reset_CMD);
	DELAY(2);
	CSR_WRITE_1(sc, CMD_REG, Rcv_Enable_CMD);

	ex_start_locked(ifp);

	DODEBUG(Start_End, printf("%s: ex_init: finish\n", ifp->if_xname););
}

static void
ex_start(struct ifnet *ifp)
{
	struct ex_softc *	sc = ifp->if_softc;

	EX_LOCK(sc);
	ex_start_locked(ifp);
	EX_UNLOCK(sc);
}

static void
ex_start_locked(struct ifnet *ifp)
{
	struct ex_softc *	sc = ifp->if_softc;
	int			i, len, data_len, avail, dest, next;
	unsigned char		tmp16[2];
	struct mbuf *		opkt;
	struct mbuf *		m;

	DODEBUG(Start_End, printf("ex_start%d: start\n", unit););

	/*
	 * Main loop: send outgoing packets to network card until there are no
	 * more packets left, or the card cannot accept any more yet.
	 */
	while (((opkt = ifp->if_snd.ifq_head) != NULL) &&
	       !(ifp->if_drv_flags & IFF_DRV_OACTIVE)) {

		/*
		 * Ensure there is enough free transmit buffer space for
		 * this packet, including its header. Note: the header
		 * cannot wrap around the end of the transmit buffer and
		 * must be kept together, so we allow space for twice the
		 * length of the header, just in case.
		 */

		for (len = 0, m = opkt; m != NULL; m = m->m_next) {
			len += m->m_len;
		}

		data_len = len;

		DODEBUG(Sent_Pkts, printf("1. Sending packet with %d data bytes. ", data_len););

		if (len & 1) {
			len += XMT_HEADER_LEN + 1;
		} else {
			len += XMT_HEADER_LEN;
		}

		if ((i = sc->tx_tail - sc->tx_head) >= 0) {
			avail = sc->tx_mem_size - i;
		} else {
			avail = -i;
		}

		DODEBUG(Sent_Pkts, printf("i=%d, avail=%d\n", i, avail););

		if (avail >= len + XMT_HEADER_LEN) {
			IF_DEQUEUE(&ifp->if_snd, opkt);

#ifdef EX_PSA_INTR      
			/*
			 * Disable rx and tx interrupts, to avoid corruption
			 * of the host address register by interrupt service
			 * routines.
			 * XXX Is this necessary with splimp() enabled?
			 */
			CSR_WRITE_1(sc, MASK_REG, All_Int);
#endif

			/*
			 * Compute the start and end addresses of this
			 * frame in the tx buffer.
			 */
			dest = sc->tx_tail;
			next = dest + len;

			if (next > sc->tx_upper_limit) {
				if ((sc->tx_upper_limit + 2 - sc->tx_tail) <=
				    XMT_HEADER_LEN) {
					dest = sc->tx_lower_limit;
					next = dest + len;
				} else {
					next = sc->tx_lower_limit +
						next - sc->tx_upper_limit - 2;
				}
			}

			/*
			 * Build the packet frame in the card's ring buffer.
			 */
			DODEBUG(Sent_Pkts, printf("2. dest=%d, next=%d. ", dest, next););

			CSR_WRITE_2(sc, HOST_ADDR_REG, dest);
			CSR_WRITE_2(sc, IO_PORT_REG, Transmit_CMD);
			CSR_WRITE_2(sc, IO_PORT_REG, 0);
			CSR_WRITE_2(sc, IO_PORT_REG, next);
			CSR_WRITE_2(sc, IO_PORT_REG, data_len);

			/*
			 * Output the packet data to the card. Ensure all
			 * transfers are 16-bit wide, even if individual
			 * mbufs have odd length.
			 */
			for (m = opkt, i = 0; m != NULL; m = m->m_next) {
				DODEBUG(Sent_Pkts, printf("[%d]", m->m_len););
				if (i) {
					tmp16[1] = *(mtod(m, caddr_t));
					CSR_WRITE_MULTI_2(sc, IO_PORT_REG,
					    (uint16_t *) tmp16, 1);
				}
				CSR_WRITE_MULTI_2(sc, IO_PORT_REG,
				    (uint16_t *) (mtod(m, caddr_t) + i),
				    (m->m_len - i) / 2);
				if ((i = (m->m_len - i) & 1) != 0) {
					tmp16[0] = *(mtod(m, caddr_t) +
						   m->m_len - 1);
				}
			}
			if (i)
				CSR_WRITE_MULTI_2(sc, IO_PORT_REG, 
				    (uint16_t *) tmp16, 1);
			/*
			 * If there were other frames chained, update the
			 * chain in the last one.
			 */
			if (sc->tx_head != sc->tx_tail) {
				if (sc->tx_tail != dest) {
					CSR_WRITE_2(sc, HOST_ADDR_REG,
					     sc->tx_last + XMT_Chain_Point);
					CSR_WRITE_2(sc, IO_PORT_REG, dest);
				}
				CSR_WRITE_2(sc, HOST_ADDR_REG,
				     sc->tx_last + XMT_Byte_Count);
				i = CSR_READ_2(sc, IO_PORT_REG);
				CSR_WRITE_2(sc, HOST_ADDR_REG,
				     sc->tx_last + XMT_Byte_Count);
				CSR_WRITE_2(sc, IO_PORT_REG, i | Ch_bit);
			}
	
			/*
			 * Resume normal operation of the card:
			 * - Make a dummy read to flush the DRAM write
			 *   pipeline.
			 * - Enable receive and transmit interrupts.
			 * - Send Transmit or Resume_XMT command, as
			 *   appropriate.
			 */
			CSR_READ_2(sc, IO_PORT_REG);
#ifdef EX_PSA_INTR
			CSR_WRITE_1(sc, MASK_REG, All_Int & ~(Rx_Int | Tx_Int));
#endif
			if (sc->tx_head == sc->tx_tail) {
				CSR_WRITE_2(sc, XMT_BAR, dest);
				CSR_WRITE_1(sc, CMD_REG, Transmit_CMD);
				sc->tx_head = dest;
				DODEBUG(Sent_Pkts, printf("Transmit\n"););
			} else {
				CSR_WRITE_1(sc, CMD_REG, Resume_XMT_List_CMD);
				DODEBUG(Sent_Pkts, printf("Resume\n"););
			}
	
			sc->tx_last = dest;
			sc->tx_tail = next;
     	 
			BPF_MTAP(ifp, opkt);

			sc->tx_timeout = 2;
			ifp->if_opackets++;
			m_freem(opkt);
		} else {
			ifp->if_drv_flags |= IFF_DRV_OACTIVE;
			DODEBUG(Status, printf("OACTIVE start\n"););
		}
	}

	DODEBUG(Start_End, printf("ex_start%d: finish\n", unit););
}

void
ex_stop(struct ex_softc *sc)
{
	
	DODEBUG(Start_End, printf("ex_stop%d: start\n", unit););

	EX_ASSERT_LOCKED(sc);
	/*
	 * Disable card operation:
	 * - Disable the interrupt line.
	 * - Flush transmission and disable reception.
	 * - Mask and clear all interrupts.
	 * - Reset the 82595.
	 */
	CSR_WRITE_1(sc, CMD_REG, Bank1_Sel);
	CSR_WRITE_1(sc, REG1, CSR_READ_1(sc, REG1) & ~TriST_INT);
	CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);
	CSR_WRITE_1(sc, CMD_REG, Rcv_Stop);
	sc->tx_head = sc->tx_tail = sc->tx_lower_limit;
	sc->tx_last = 0; /* XXX I think these two lines are not necessary, because ex_init will always be called again to reinit the interface. */
	CSR_WRITE_1(sc, MASK_REG, All_Int);
	CSR_WRITE_1(sc, STATUS_REG, All_Int);
	CSR_WRITE_1(sc, CMD_REG, Reset_CMD);
	DELAY(200);
	sc->ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
	sc->tx_timeout = 0;
	callout_stop(&sc->timer);

	DODEBUG(Start_End, printf("ex_stop%d: finish\n", unit););

	return;
}

void
ex_intr(void *arg)
{
	struct ex_softc *sc = (struct ex_softc *)arg;
	struct ifnet 	*ifp = sc->ifp;
	int		int_status, send_pkts;
	int		loops = 100;

	DODEBUG(Start_End, printf("ex_intr%d: start\n", unit););

	EX_LOCK(sc);
	send_pkts = 0;
	while (loops-- > 0 &&
	    (int_status = CSR_READ_1(sc, STATUS_REG)) & (Tx_Int | Rx_Int)) {
		/* don't loop forever */
		if (int_status == 0xff)
			break;
		if (int_status & Rx_Int) {
			CSR_WRITE_1(sc, STATUS_REG, Rx_Int);
			ex_rx_intr(sc);
		} else if (int_status & Tx_Int) {
			CSR_WRITE_1(sc, STATUS_REG, Tx_Int);
			ex_tx_intr(sc);
			send_pkts = 1;
		}
	}
	if (loops == 0)
		printf("100 loops are not enough\n");

	/*
	 * If any packet has been transmitted, and there are queued packets to
	 * be sent, attempt to send more packets to the network card.
	 */
	if (send_pkts && (ifp->if_snd.ifq_head != NULL))
		ex_start_locked(ifp);
	EX_UNLOCK(sc);

	DODEBUG(Start_End, printf("ex_intr%d: finish\n", unit););

	return;
}

static void
ex_tx_intr(struct ex_softc *sc)
{
	struct ifnet *	ifp = sc->ifp;
	int		tx_status;

	DODEBUG(Start_End, printf("ex_tx_intr%d: start\n", unit););

	/*
	 * - Cancel the watchdog.
	 * For all packets transmitted since last transmit interrupt:
	 * - Advance chain pointer to next queued packet.
	 * - Update statistics.
	 */

	sc->tx_timeout = 0;

	while (sc->tx_head != sc->tx_tail) {
		CSR_WRITE_2(sc, HOST_ADDR_REG, sc->tx_head);

		if (!(CSR_READ_2(sc, IO_PORT_REG) & Done_bit))
			break;

		tx_status = CSR_READ_2(sc, IO_PORT_REG);
		sc->tx_head = CSR_READ_2(sc, IO_PORT_REG);

		if (tx_status & TX_OK_bit) {
			ifp->if_opackets++;
		} else {
			ifp->if_oerrors++;
		}

		ifp->if_collisions += tx_status & No_Collisions_bits;
	}

	/*
	 * The card should be ready to accept more packets now.
	 */

	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;

	DODEBUG(Status, printf("OIDLE tx_intr\n"););
	DODEBUG(Start_End, printf("ex_tx_intr%d: finish\n", unit););

	return;
}

static void
ex_rx_intr(struct ex_softc *sc)
{
	struct ifnet *		ifp = sc->ifp;
	int			rx_status;
	int			pkt_len;
	int			QQQ;
	struct mbuf *		m;
	struct mbuf *		ipkt;
	struct ether_header *	eh;

	DODEBUG(Start_End, printf("ex_rx_intr%d: start\n", unit););

	/*
	 * For all packets received since last receive interrupt:
	 * - If packet ok, read it into a new mbuf and queue it to interface,
	 *   updating statistics.
	 * - If packet bad, just discard it, and update statistics.
	 * Finally, advance receive stop limit in card's memory to new location.
	 */

	CSR_WRITE_2(sc, HOST_ADDR_REG, sc->rx_head);

	while (CSR_READ_2(sc, IO_PORT_REG) == RCV_Done) {

		rx_status = CSR_READ_2(sc, IO_PORT_REG);
		sc->rx_head = CSR_READ_2(sc, IO_PORT_REG);
		QQQ = pkt_len = CSR_READ_2(sc, IO_PORT_REG);

		if (rx_status & RCV_OK_bit) {
			MGETHDR(m, M_DONTWAIT, MT_DATA);
			ipkt = m;
			if (ipkt == NULL) {
				ifp->if_iqdrops++;
			} else {
				ipkt->m_pkthdr.rcvif = ifp;
				ipkt->m_pkthdr.len = pkt_len;
				ipkt->m_len = MHLEN;

				while (pkt_len > 0) {
					if (pkt_len >= MINCLSIZE) {
						MCLGET(m, M_DONTWAIT);
						if (m->m_flags & M_EXT) {
							m->m_len = MCLBYTES;
						} else {
							m_freem(ipkt);
							ifp->if_iqdrops++;
							goto rx_another;
						}
					}
					m->m_len = min(m->m_len, pkt_len);

	  /*
	   * NOTE: I'm assuming that all mbufs allocated are of even length,
	   * except for the last one in an odd-length packet.
	   */

					CSR_READ_MULTI_2(sc, IO_PORT_REG,
					    mtod(m, uint16_t *), m->m_len / 2);

					if (m->m_len & 1) {
						*(mtod(m, caddr_t) + m->m_len - 1) = CSR_READ_1(sc, IO_PORT_REG);
					}
					pkt_len -= m->m_len;

					if (pkt_len > 0) {
						MGET(m->m_next, M_DONTWAIT, MT_DATA);
						if (m->m_next == NULL) {
							m_freem(ipkt);
							ifp->if_iqdrops++;
							goto rx_another;
						}
						m = m->m_next;
						m->m_len = MLEN;
					}
				}
				eh = mtod(ipkt, struct ether_header *);
#ifdef EXDEBUG
	if (debug_mask & Rcvd_Pkts) {
		if ((eh->ether_dhost[5] != 0xff) || (eh->ether_dhost[0] != 0xff)) {
			printf("Receive packet with %d data bytes: %6D -> ", QQQ, eh->ether_shost, ":");
			printf("%6D\n", eh->ether_dhost, ":");
		} /* QQQ */
	}
#endif
				EX_UNLOCK(sc);
				(*ifp->if_input)(ifp, ipkt);
				EX_LOCK(sc);
				ifp->if_ipackets++;
			}
		} else {
			ifp->if_ierrors++;
		}
		CSR_WRITE_2(sc, HOST_ADDR_REG, sc->rx_head);
rx_another: ;
	}

	if (sc->rx_head < sc->rx_lower_limit + 2)
		CSR_WRITE_2(sc, RCV_STOP_REG, sc->rx_upper_limit);
	else
		CSR_WRITE_2(sc, RCV_STOP_REG, sc->rx_head - 2);

	DODEBUG(Start_End, printf("ex_rx_intr%d: finish\n", unit););

	return;
}


static int
ex_ioctl(register struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct ex_softc *	sc = ifp->if_softc;
	struct ifreq *		ifr = (struct ifreq *)data;
	int			error = 0;

	DODEBUG(Start_End, printf("%s: ex_ioctl: start ", ifp->if_xname););

	switch(cmd) {
		case SIOCSIFADDR:
		case SIOCGIFADDR:
		case SIOCSIFMTU:
			error = ether_ioctl(ifp, cmd, data);
			break;

		case SIOCSIFFLAGS:
			DODEBUG(Start_End, printf("SIOCSIFFLAGS"););
			EX_LOCK(sc);
			if ((ifp->if_flags & IFF_UP) == 0 &&
			    (ifp->if_drv_flags & IFF_DRV_RUNNING)) {
				ex_stop(sc);
			} else {
      				ex_init_locked(sc);
			}
			EX_UNLOCK(sc);
			break;
		case SIOCADDMULTI:
		case SIOCDELMULTI:
			ex_init(sc);
			error = 0;
			break;
		case SIOCSIFMEDIA:
		case SIOCGIFMEDIA:
			error = ifmedia_ioctl(ifp, ifr, &sc->ifmedia, cmd);
			break;
		default:
			DODEBUG(Start_End, printf("unknown"););
			error = EINVAL;
	}

	DODEBUG(Start_End, printf("\n%s: ex_ioctl: finish\n", ifp->if_xname););

	return(error);
}

static void
ex_setmulti(struct ex_softc *sc)
{
	struct ifnet *ifp;
	struct ifmultiaddr *maddr;
	uint16_t *addr;
	int count;
	int timeout, status;
	
	ifp = sc->ifp;

	count = 0;
	if_maddr_rlock(ifp);
	TAILQ_FOREACH(maddr, &ifp->if_multiaddrs, ifma_link) {
		if (maddr->ifma_addr->sa_family != AF_LINK)
			continue;
		count++;
	}
	if_maddr_runlock(ifp);

	if ((ifp->if_flags & IFF_PROMISC) || (ifp->if_flags & IFF_ALLMULTI)
			|| count > 63) {
		/* Interface is in promiscuous mode or there are too many
		 * multicast addresses for the card to handle */
		CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
		CSR_WRITE_1(sc, REG2, CSR_READ_1(sc, REG2) | Promisc_Mode);
		CSR_WRITE_1(sc, REG3, CSR_READ_1(sc, REG3));
		CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);
	}
	else if ((ifp->if_flags & IFF_MULTICAST) && (count > 0)) {
		/* Program multicast addresses plus our MAC address
		 * into the filter */
		CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
		CSR_WRITE_1(sc, REG2, CSR_READ_1(sc, REG2) | Multi_IA);
		CSR_WRITE_1(sc, REG3, CSR_READ_1(sc, REG3));
		CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);

		/* Borrow space from TX buffer; this should be safe
		 * as this is only called from ex_init */
		
		CSR_WRITE_2(sc, HOST_ADDR_REG, sc->tx_lower_limit);
		CSR_WRITE_2(sc, IO_PORT_REG, MC_Setup_CMD);
		CSR_WRITE_2(sc, IO_PORT_REG, 0);
		CSR_WRITE_2(sc, IO_PORT_REG, 0);
		CSR_WRITE_2(sc, IO_PORT_REG, (count + 1) * 6);

		if_maddr_rlock(ifp);
		TAILQ_FOREACH(maddr, &ifp->if_multiaddrs, ifma_link) {
			if (maddr->ifma_addr->sa_family != AF_LINK)
				continue;

			addr = (uint16_t*)LLADDR((struct sockaddr_dl *)
					maddr->ifma_addr);
			CSR_WRITE_2(sc, IO_PORT_REG, *addr++);
			CSR_WRITE_2(sc, IO_PORT_REG, *addr++);
			CSR_WRITE_2(sc, IO_PORT_REG, *addr++);
		}
		if_maddr_runlock(ifp);

		/* Program our MAC address as well */
		/* XXX: Is this necessary?  The Linux driver does this
		 * but the NetBSD driver does not */
		addr = (uint16_t*)IF_LLADDR(sc->ifp);
		CSR_WRITE_2(sc, IO_PORT_REG, *addr++);
		CSR_WRITE_2(sc, IO_PORT_REG, *addr++);
		CSR_WRITE_2(sc, IO_PORT_REG, *addr++);

		CSR_READ_2(sc, IO_PORT_REG);
		CSR_WRITE_2(sc, XMT_BAR, sc->tx_lower_limit);
		CSR_WRITE_1(sc, CMD_REG, MC_Setup_CMD);

		sc->tx_head = sc->tx_lower_limit;
		sc->tx_tail = sc->tx_head + XMT_HEADER_LEN + (count + 1) * 6;

		for (timeout=0; timeout<100; timeout++) {
			DELAY(2);
			if ((CSR_READ_1(sc, STATUS_REG) & Exec_Int) == 0)
				continue;

			status = CSR_READ_1(sc, CMD_REG);
			CSR_WRITE_1(sc, STATUS_REG, Exec_Int);
			break;
		}

		sc->tx_head = sc->tx_tail;
	}
	else
	{
		/* No multicast or promiscuous mode */
		CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
		CSR_WRITE_1(sc, REG2, CSR_READ_1(sc, REG2) & 0xDE);
			/* ~(Multi_IA | Promisc_Mode) */
		CSR_WRITE_1(sc, REG3, CSR_READ_1(sc, REG3));
		CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);
	}
}

static void
ex_reset(struct ex_softc *sc)
{

	DODEBUG(Start_End, printf("ex_reset%d: start\n", unit););

	EX_ASSERT_LOCKED(sc);
	ex_stop(sc);
	ex_init_locked(sc);

	DODEBUG(Start_End, printf("ex_reset%d: finish\n", unit););

	return;
}

static void
ex_watchdog(void *arg)
{
	struct ex_softc *	sc = arg;
	struct ifnet *ifp = sc->ifp;

	if (sc->tx_timeout && --sc->tx_timeout == 0) {
		DODEBUG(Start_End, if_printf(ifp, "ex_watchdog: start\n"););

		ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;

		DODEBUG(Status, printf("OIDLE watchdog\n"););

		ifp->if_oerrors++;
		ex_reset(sc);
		ex_start_locked(ifp);

		DODEBUG(Start_End, if_printf(ifp, "ex_watchdog: finish\n"););
	}

	callout_reset(&sc->timer, hz, ex_watchdog, sc);
}

static int
ex_get_media(struct ex_softc *sc)
{
	int	current;
	int	media;

	media = ex_eeprom_read(sc, EE_W5);

	CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
	current = CSR_READ_1(sc, REG3);
	CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);

	if ((current & TPE_bit) && (media & EE_W5_PORT_TPE))
		return(IFM_ETHER|IFM_10_T);
	if ((current & BNC_bit) && (media & EE_W5_PORT_BNC))
		return(IFM_ETHER|IFM_10_2);

	if (media & EE_W5_PORT_AUI)
		return (IFM_ETHER|IFM_10_5);

	return (IFM_ETHER|IFM_AUTO);
}

static int
ex_ifmedia_upd(ifp)
	struct ifnet *		ifp;
{
	struct ex_softc *       sc = ifp->if_softc;

	if (IFM_TYPE(sc->ifmedia.ifm_media) != IFM_ETHER)
		return EINVAL;

	return (0);
}

static void
ex_ifmedia_sts(ifp, ifmr)
	struct ifnet *          ifp;
	struct ifmediareq *     ifmr;
{
	struct ex_softc *       sc = ifp->if_softc;

	EX_LOCK(sc);
	ifmr->ifm_active = ex_get_media(sc);
	ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
	EX_UNLOCK(sc);

	return;
}

u_short
ex_eeprom_read(struct ex_softc *sc, int location)
{
	int i;
	u_short data = 0;
	int read_cmd = location | EE_READ_CMD;
	short ctrl_val = EECS;

	CSR_WRITE_1(sc, CMD_REG, Bank2_Sel);
	CSR_WRITE_1(sc, EEPROM_REG, EECS);
	for (i = 8; i >= 0; i--) {
		short outval = (read_cmd & (1 << i)) ? ctrl_val | EEDI : ctrl_val;
		CSR_WRITE_1(sc, EEPROM_REG, outval);
		CSR_WRITE_1(sc, EEPROM_REG, outval | EESK);
		DELAY(3);
		CSR_WRITE_1(sc, EEPROM_REG, outval);
		DELAY(2);
	}
	CSR_WRITE_1(sc, EEPROM_REG, ctrl_val);

	for (i = 16; i > 0; i--) {
		CSR_WRITE_1(sc, EEPROM_REG, ctrl_val | EESK);
		DELAY(3);
		data = (data << 1) | 
		    ((CSR_READ_1(sc, EEPROM_REG) & EEDO) ? 1 : 0);
		CSR_WRITE_1(sc, EEPROM_REG, ctrl_val);
		DELAY(2);
	}

	ctrl_val &= ~EECS;
	CSR_WRITE_1(sc, EEPROM_REG, ctrl_val | EESK);
	DELAY(3);
	CSR_WRITE_1(sc, EEPROM_REG, ctrl_val);
	DELAY(2);
	CSR_WRITE_1(sc, CMD_REG, Bank0_Sel);
	return(data);
}
