/*	$NetBSD: tropic.c,v 1.44 2012/10/27 17:18:23 chs Exp $	*/

/*
 * Ported to NetBSD by Onno van der Linden
 * Many thanks to Larry Lile for sending me the IBM TROPIC documentation.
 *
 * Mach Operating System
 * Copyright (c) 1991 Carnegie Mellon University
 * Copyright (c) 1991 IBM Corporation
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation,
 * and that the name IBM not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written
 * prior permission.
 *
 * CARNEGIE MELLON AND IBM ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON AND IBM DISCLAIM ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tropic.c,v 1.44 2012/10/27 17:18:23 chs Exp $");

#include "opt_inet.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/mbuf.h>
#include <sys/buf.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/device.h>

#include <net/if.h>
#include <net/if_llc.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/netisr.h>
#include <net/route.h>
#include <net/if_token.h>

#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_var.h>
#endif


#include <net/bpf.h>
#include <net/bpfdesc.h>

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

#include <dev/ic/tropicreg.h>
#include <dev/ic/tropicvar.h>

static void tr_shutdown(void *);
static void tr_reopen(void *);

void	tr_rint(struct tr_softc *);
void	tr_xint(struct tr_softc *);
void	tr_oldxint(struct tr_softc *);
struct	mbuf *tr_get(struct tr_softc *, int, struct ifnet *);
void	tr_opensap(struct tr_softc *, u_char);
int	tr_mbcopy(struct tr_softc *, bus_size_t, struct mbuf *);
void	tr_bcopy(struct tr_softc *, u_char *, int);
void	tr_start(struct ifnet *);
void	tr_oldstart(struct ifnet *);
void	tr_watchdog(struct ifnet *);
int	tr_mediachange(struct ifnet *);
void	tr_mediastatus(struct ifnet *, struct ifmediareq *);
int	tropic_mediachange(struct tr_softc *);
void	tropic_mediastatus(struct tr_softc *, struct ifmediareq *);
void	tr_reinit(void *);

/*
 * TODO:
 * clean up tr_intr: more subroutines
 * IFF_LINK0 == IFM_TOK_SRCRT change to link flag implies media flag change
 * IFF_LINK1 == IFM_TOK_ALLR  change to link flag implies media flag change
 * XXX Create receive_done queue to kill "ASB not free", but does this ever
 * XXX happen ?
 */

static	int media[] = {
	IFM_TOKEN | IFM_TOK_UTP4,
	IFM_TOKEN | IFM_TOK_STP4,
	IFM_TOKEN | IFM_TOK_UTP16,
	IFM_TOKEN | IFM_TOK_STP16,
	IFM_TOKEN | IFM_TOK_UTP4,
	IFM_TOKEN | IFM_TOK_UTP16,
	IFM_TOKEN | IFM_TOK_STP4,
	IFM_TOKEN | IFM_TOK_STP16
};

int
tropic_mediachange(struct tr_softc *sc)
{
	if (IFM_TYPE(sc->sc_media.ifm_media) != IFM_TOKEN)
		return EINVAL;

	switch (IFM_SUBTYPE(sc->sc_media.ifm_media)) {
	case IFM_TOK_STP16:
	case IFM_TOK_UTP16:
		if ((sc->sc_init_status & RSP_16) == 0) {
			tr_stop(sc);
			if (tr_setspeed(sc, 16))
				return EINVAL;
			if (tr_reset(sc))
				return EINVAL;
			if (tr_config(sc))
				return EINVAL;
		}
		break;
	case IFM_TOK_STP4:
	case IFM_TOK_UTP4:
		if ((sc->sc_init_status & RSP_16) != 0) {
			tr_stop(sc);
			if (tr_setspeed(sc, 4))
				return EINVAL;
			if (tr_reset(sc))
				return EINVAL;
			if (tr_config(sc))
				return EINVAL;
		}
		break;
	}
/*
 * XXX Handle Early Token Release !!!!
 */
	return 0;
}

void
tropic_mediastatus(struct tr_softc *sc, struct ifmediareq *ifmr)
{
	struct ifmedia	*ifm = &sc->sc_media;

	ifmr->ifm_active = ifm->ifm_cur->ifm_media;
}

int
tr_config(struct tr_softc *sc)
{
	if (sc->sc_init_status & FAST_PATH_TRANSMIT) {
		int i;

		for (i=0; i < SRB_CFP_CMDSIZE; i++)
			SRB_OUTB(sc, sc->sc_srb, i, 0);

		SRB_OUTB(sc, sc->sc_srb, SRB_CMD, DIR_CONFIG_FAST_PATH_RAM);

		SRB_OUTW(sc, sc->sc_srb, SRB_CFP_RAMSIZE,
		    (16 + (sc->sc_nbuf * FP_BUF_LEN) / 8));
		SRB_OUTW(sc, sc->sc_srb, SRB_CFP_BUFSIZE, FP_BUF_LEN);

		/* tell adapter: command in SRB */
		ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);

		for (i = 0; i < 30000; i++) {
			if (ACA_RDB(sc, ACA_ISRP_o) & SRB_RESP_INT)
				break;
			delay(100);
		}

		if (i == 30000 && sc->sc_srb == ACA_RDW(sc, ACA_WRBR)) {
			aprint_error_dev(sc->sc_dev, "no response for fast path cfg\n");
			return 1;
		}

		ACA_RSTB(sc, ACA_ISRP_o, ~(SRB_RESP_INT));

		if ((SRB_INB(sc, sc->sc_srb, SRB_RETCODE) != 0)) {
			printf("%s: cfg fast path returned: 0x%02x\n",
			    device_xname(sc->sc_dev),
			    SRB_INB(sc, sc->sc_srb, SRB_RETCODE));
			return 1;
		}

		sc->sc_txca = SRB_INW(sc, sc->sc_srb, SRB_CFPRESP_FPXMIT);
		sc->sc_srb = SRB_INW(sc, sc->sc_srb, SRB_CFPRESP_SRBADDR);
	}
	else {
		if (sc->sc_init_status & RSP_16)
			sc->sc_maxmtu = sc->sc_dhb16maxsz;
		else
			sc->sc_maxmtu = sc->sc_dhb4maxsz;
/*
 * XXX Not completely true because Fast Path Transmit has 514 byte buffers
 * XXX and TR_MAX_LINK_HDR is only correct when source-routing is used.
 * XXX depending on whether source routing is used change the calculation
 * XXX use IFM_TOK_SRCRT (IFF_LINK0)
 * XXX recompute sc_minbuf !!
 */
		sc->sc_maxmtu -= TR_MAX_LINK_HDR;
	}
	return 0;
}

int
tr_attach(struct tr_softc *sc)
{
	int	nmedia, *mediaptr, *defmediaptr;
	int	i, temp;
	u_int8_t myaddr[ISO88025_ADDR_LEN];
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;

	if (sc->sc_init_status & FAST_PATH_TRANSMIT) {
		int	numbuf = 0;

		switch (sc->sc_memsize) {
		case 65536:
			numbuf = 58;
			sc->sc_maxmtu = IPMTU_4MBIT_MAX;
			break;
		case 32768:
			numbuf = 29;
			sc->sc_maxmtu = IPMTU_4MBIT_MAX;
			break;
		case 16384:
			numbuf = 13;
			sc->sc_maxmtu = IPMTU_4MBIT_MAX;
			break;
		case 8192:
			numbuf = 5;
			sc->sc_maxmtu = ISO88025_MTU;
		}

		sc->sc_minbuf = ((sc->sc_maxmtu + 511) / 512) + 1;
		sc->sc_nbuf = numbuf;

/*
 *  Create circular queues caching the buffer pointers ?
 */
	}
	else {
/*
 * MAX_MACFRAME_SIZE = DHB_SIZE - 6
 * IPMTU = MAX_MACFRAME_SIZE - (14 + 18 + 8)
 * (14 = header, 18 = sroute, 8 = llcsnap)
 */

		switch (sc->sc_memsize) {
		case 8192:
			sc->sc_dhb4maxsz = 2048;
			sc->sc_dhb16maxsz = 2048;
			break;
		case 16384:
			sc->sc_dhb4maxsz = 4096;
			sc->sc_dhb16maxsz = 4096;
			break;
		case 32768:
			sc->sc_dhb4maxsz = 4464;
			sc->sc_dhb16maxsz = 8192;
			break;
		case 65536:
			sc->sc_dhb4maxsz = 4464;
			sc->sc_dhb16maxsz = 8192;
			break;
		}
		switch (MM_INB(sc, TR_DHB4_OFFSET)) {
		case 0xF:
			if (sc->sc_dhb4maxsz > 2048)
				sc->sc_dhb4maxsz = 2048;
			break;
		case 0xE:
			if (sc->sc_dhb4maxsz > 4096)
				sc->sc_dhb4maxsz = 4096;
			break;
		case 0xD:
			if (sc->sc_dhb4maxsz > 4464)
				sc->sc_dhb4maxsz = 4464;
			break;
		}

		switch (MM_INB(sc, TR_DHB16_OFFSET)) {
		case 0xF:
			if (sc->sc_dhb16maxsz > 2048)
				sc->sc_dhb16maxsz = 2048;
			break;
		case 0xE:
			if (sc->sc_dhb16maxsz > 4096)
				sc->sc_dhb16maxsz = 4096;
			break;
		case 0xD:
			if (sc->sc_dhb16maxsz > 8192)
				sc->sc_dhb16maxsz = 8192;
			break;
		case 0xC:
			if (sc->sc_dhb16maxsz > 8192)
				sc->sc_dhb16maxsz = 8192;
			break;
		case 0xB:
			if (sc->sc_dhb16maxsz > 8192)
				sc->sc_dhb16maxsz = 8192;
			break;
		}
	}

	if (tr_config(sc))
		return 1;

	/*
	 * init network-visible interface
	 */
	strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
	ifp->if_softc = sc;
	ifp->if_ioctl = tr_ioctl;
	if (sc->sc_init_status & FAST_PATH_TRANSMIT)
		ifp->if_start = tr_start;
	else
		ifp->if_start = tr_oldstart;
	ifp->if_flags = IFF_BROADCAST | IFF_NOTRAILERS;
	ifp->if_watchdog = tr_watchdog;
	IFQ_SET_READY(&ifp->if_snd);

	switch (MM_INB(sc, TR_MEDIAS_OFFSET)) {
	case 0xF:
		nmedia = 1;
		mediaptr = &media[6];
		break;
	case 0xE:
		nmedia = 2;
		mediaptr = &media[0];
		break;
	case 0xD:
		nmedia = 1;
		mediaptr = &media[4];
		break;
	default:
		nmedia = 0;
		mediaptr = NULL;
	}

	switch (MM_INB(sc, TR_RATES_OFFSET)) {
	case 0xF:
		/* 4 Mbps */
		break;
	case 0xE:
		/* 16 Mbps */
		if (mediaptr)
			mediaptr += nmedia;
		break;
	case 0xD:
		/* 4/16 Mbps */
		nmedia *= 2;
		break;
	}

	switch (MM_INB(sc, TR_MEDIA_OFFSET)) {
	case 0xF:
		/* STP */
		defmediaptr = &media[6];
		break;
	case 0xE:
		/* UTP */
		defmediaptr = &media[4];
		break;
	case 0xD:
		/* STP and UTP == a single shielded RJ45 which supports both */
		/* XXX additional types in net/if_media.h ?? */
		defmediaptr = &media[4];
		break;
	default:
		defmediaptr = NULL;
	}

	if (defmediaptr && (sc->sc_init_status & RSP_16))
		++defmediaptr;

	if (sc->sc_mediachange == NULL && sc->sc_mediastatus == NULL) {
		switch (MM_INB(sc, TR_TYP_OFFSET)) {
		case 0x0D:
		case 0x0C:
			sc->sc_mediachange = tropic_mediachange;
			sc->sc_mediastatus = tropic_mediastatus;
		}
	}

	ifmedia_init(&sc->sc_media, 0, tr_mediachange, tr_mediastatus);
	if (mediaptr != NULL) {
		for (i = 0; i < nmedia; i++)
			ifmedia_add(&sc->sc_media, mediaptr[i], 0, NULL);
		if (defmediaptr)
			ifmedia_set(&sc->sc_media, *defmediaptr);
		else
			ifmedia_set(&sc->sc_media, 0);
	}
	else {
		ifmedia_add(&sc->sc_media, IFM_TOKEN | IFM_MANUAL, 0, NULL);
		ifmedia_set(&sc->sc_media, IFM_TOKEN | IFM_MANUAL);
	}

	if_attach(ifp);

	for (i = 0, temp = 0; i < ISO88025_ADDR_LEN; i++, temp += 4) {
		myaddr[i] = (MM_INB(sc, (TR_MAC_OFFSET + temp)) & 0xf) << 4;
		myaddr[i] |= MM_INB(sc, (TR_MAC_OFFSET + temp + 2)) & 0xf;
	}

	token_ifattach(ifp, myaddr);

	printf("%s: address %s ring speed %d Mbps\n", device_xname(sc->sc_dev),
	    token_sprintf(myaddr), (sc->sc_init_status & RSP_16) ? 16 : 4);

	callout_init(&sc->sc_init_callout, 0);
	callout_init(&sc->sc_reinit_callout, 0);

	sc->sc_sdhook = shutdownhook_establish(tr_shutdown, sc);
	return 0;
}

int
tr_setspeed(struct tr_softc *sc, u_int8_t speed)
{
	SRB_OUTB(sc, sc->sc_srb, SRB_CMD, DIR_SET_DEFAULT_RING_SPEED);
	SRB_OUTB(sc, sc->sc_srb, CMD_RETCODE, 0xfe);
	SRB_OUTB(sc, sc->sc_srb, SRB_SET_DEFRSP, speed);
	/* Tell adapter: command in SRB. */
	ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);

	/* Wait for it to complete. */
	tr_sleep(sc);

	if ((SRB_INB(sc, sc->sc_srb, SRB_RETCODE) != 0)) {
		printf("%s: set default ringspeed returned: 0x%02x\n",
		    device_xname(sc->sc_dev), SRB_INB(sc, sc->sc_srb, SRB_RETCODE));
		return 1;
	}
	return 0;
}

int
tr_mediachange(struct ifnet *ifp)
{
	struct tr_softc *sc = ifp->if_softc;

	if (sc->sc_mediachange)
		return ((*sc->sc_mediachange)(sc));
	return EINVAL;
}

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

/* set LINK0 and/or LINK1 */
	if (sc->sc_mediastatus)
		(*sc->sc_mediastatus)(sc, ifmr);
}

int
tr_reset(struct tr_softc *sc)
{
	int i;

	sc->sc_srb = 0;

	/*
	 * Reset the card.
	 */
	/* latch on an unconditional adapter reset */
	bus_space_write_1(sc->sc_piot, sc->sc_pioh, TR_RESET, 0);
	delay(50000); /* delay 50ms */
	/*
	 * XXX set paging if we have the right type of card
	 */
	/* turn off adapter reset */
	bus_space_write_1(sc->sc_piot, sc->sc_pioh, TR_RELEASE, 0);

	/* Enable interrupts. */

	ACA_SETB(sc, ACA_ISRP_e, INT_ENABLE);

	/* Wait for an answer from the adapter. */

	for (i = 0; i < 35000; i++) {
		if (ACA_RDB(sc, ACA_ISRP_o) & SRB_RESP_INT)
			break;
		delay(100);
	}

	if (i == 35000 && sc->sc_srb == 0) {
		aprint_error_dev(sc->sc_dev, "no response from adapter after reset\n");
		return 1;
	}

	ACA_RSTB(sc, ACA_ISRP_o, ~(SRB_RESP_INT));

	ACA_OUTB(sc, ACA_RRR_e, (sc->sc_maddr >> 12));
	sc->sc_srb = ACA_RDW(sc, ACA_WRBR);
	if (SRB_INB(sc, sc->sc_srb, SRB_CMD) != 0x80) {
		aprint_error_dev(sc->sc_dev, "initialization incomplete, status: 0x%02x\n",
		    SRB_INB(sc, sc->sc_srb, SRB_CMD));
		return 1;
	}
	if (SRB_INB(sc, sc->sc_srb, SRB_INIT_BUC) != 0) {
		aprint_error_dev(sc->sc_dev, "Bring Up Code %02x\n",
		    SRB_INB(sc, sc->sc_srb, SRB_INIT_BUC));
		return 1;
	}

	sc->sc_init_status = SRB_INB(sc, sc->sc_srb, SRB_INIT_STATUS);

	sc->sc_xmit_head = sc->sc_xmit_tail = 0;

	/* XXX should depend on sc_resvdmem. */
	if (MM_INB(sc, TR_RAM_OFFSET) == 0xB && sc->sc_memsize == 65536)
		for (i = 0; i < 512; i++)
			SR_OUTB(sc, 0xfe00 + i, 0);
	return 0;
}

/*
 * tr_stop - stop interface (issue a DIR.CLOSE.ADAPTER command)
 */
void
tr_stop(struct tr_softc *sc)
{
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;

	if ((ifp->if_flags & IFF_RUNNING) != 0) {
/*
 * transmitter cannot be used from now on
 */
		ifp->if_flags |= IFF_OACTIVE;

		/* Close command. */
		SRB_OUTB(sc, sc->sc_srb, SRB_CMD, DIR_CLOSE);
		/* Tell adapter: command in SRB. */
		ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);

		/* Wait for it to complete. */
		tr_sleep(sc);
		sc->sc_srb = ACA_RDW(sc, ACA_WRBR);
	}
}

static void
tr_shutdown(void *arg)
{
	struct tr_softc *sc = arg;

	tr_stop(sc);
}

void
tr_reinit(void *arg)
{
	struct tr_softc *sc = arg;
	int	s;

	s = splnet();
	if (tr_reset(sc) == 0) {
		if (tr_config(sc) == 0)
			tr_init(arg);
	}
	splx(s);
}

static void
tr_reopen(void *arg)
{
	int	s;

	s = splnet();
	tr_init(arg);
	splx(s);
}

/*
 *  tr_init - initialize network interface, open adapter for packet
 *          - reception and start any pending output
 *          - must be called at splnet
 */
void
tr_init(void *arg)
{
	struct tr_softc *sc = arg;
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
	bus_size_t open_srb;
	int	num_dhb, resvdmem, availmem, dhbsize;

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

	ifp->if_flags &= ~IFF_OACTIVE;
	sc->sc_xmit_head = sc->sc_xmit_tail = 0;	/* XXX tr_reset() */

	open_srb = sc->sc_srb;

	/* Zero SRB. */
	bus_space_set_region_1(sc->sc_memt, sc->sc_sramh,
	    open_srb, 0, SRB_OPEN_CMDSIZE);

	/* Open command. */
	SRB_OUTB(sc, open_srb, SRB_CMD, DIR_OPEN_ADAPTER);
/*
 * XXX handle IFM_TOK_ETR !!!!
 */
	/* Set open parameters in SRB. */
	SRB_OUTW(sc, open_srb, SRB_OPEN_OPTIONS, OPEN_PASS_BCON_MAC);

	num_dhb = 1;

	if ((sc->sc_init_status & FAST_PATH_TRANSMIT) == 0) {
		availmem = sc->sc_memsize;
		resvdmem = RESVDMEM_SIZE + sc->sc_memreserved;

		/* allow MAX of two SAPS */
		SRB_OUTB(sc, open_srb, SRB_OPEN_DLCMAXSAP, 2);
		resvdmem += 2 * SAPCB_SIZE;

		/* allow MAX of 4 stations */
		SRB_OUTB(sc, open_srb, SRB_OPEN_DLCMAXSTA, 4);
		resvdmem += 4 * LSCB_SIZE;

		if (sc->sc_init_status & RSP_16) {
			dhbsize = sc->sc_dhb16maxsz;
		}
		else {
			dhbsize = sc->sc_dhb4maxsz;
		}
#if 0	/* XXXchb unneeded? */
		if (dhbsize > 2048)
			num_dhb = 2;
#endif
		SRB_OUTW(sc, open_srb, SRB_OPEN_DHBLEN, dhbsize);
		sc->sc_nbuf = (dhbsize + 511) / 512;
		/*
		 * Try to leave room for two fullsized packets when
		 * requesting DHBs.
		 */
		availmem -= resvdmem;
		num_dhb = (availmem / dhbsize) - 2;
		if (num_dhb > 2)
			num_dhb = 2;	/* firmware can't cope with more DHBs */
		if (num_dhb < 1)
			num_dhb = 1;	/* we need at least one */
	}
	else
		SRB_OUTW(sc, open_srb, SRB_OPEN_DHBLEN, DHB_LENGTH);

	SRB_OUTB(sc, open_srb, SRB_OPEN_NUMDHB, num_dhb);
	SRB_OUTW(sc, open_srb, SRB_OPEN_RCVBUFLEN, RCV_BUF_LEN);
	SRB_OUTW(sc, open_srb, SRB_OPEN_NUMRCVBUF, sc->sc_nbuf);

	/* Tell adapter: command in SRB. */
	ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);

}

/*
 *  tr_oldstart - Present transmit request to adapter
 */
void
tr_oldstart(struct ifnet *ifp)
{
	struct tr_softc *sc = ifp->if_softc;
	bus_size_t srb = sc->sc_srb;

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

	ifp->if_flags |= IFF_OACTIVE;

	/* Load SRB to request transmit. */
	SRB_OUTB(sc, srb, SRB_CMD, XMIT_UI_FRM);
	SRB_OUTW(sc, srb, XMIT_STATIONID, sc->exsap_station);
	ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);
}

void
tr_start(struct ifnet *ifp)
{
	struct tr_softc *sc = ifp->if_softc;
	bus_size_t first_txbuf, txbuf;
	struct mbuf	*m0, *m;
	int	size, bufspace;
	bus_size_t framedata;

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


next:
	if (sc->sc_xmit_buffers < sc->sc_minbuf)
		return;

	/* if data in queue, copy mbuf chain to fast path buffers */
	IFQ_DEQUEUE(&ifp->if_snd, m0);

	if (m0 == 0)
		return;
	bpf_mtap(ifp, m0);
	first_txbuf = txbuf = TXCA_INW(sc, TXCA_FREE_QUEUE_HEAD) - XMIT_NEXTBUF;
	framedata = txbuf + XMIT_FP_DATA;
	size = 0;
	bufspace = FP_BUF_LEN - XMIT_FP_DATA;
	for (m = m0; m; m = m->m_next) {
		int len = m->m_len;
		char *ptr = mtod(m, char *);

		while (len >= bufspace) {
			--sc->sc_xmit_buffers;
			bus_space_write_region_1(sc->sc_memt, sc->sc_sramh,
			    framedata, ptr, bufspace);
			size += bufspace;
			ptr += bufspace;
			len -= bufspace;
			TXB_OUTW(sc, txbuf, XMIT_BUFLEN,
			    (FP_BUF_LEN - XMIT_FP_DATA));
			txbuf = TXB_INW(sc, txbuf, XMIT_NEXTBUF) - XMIT_NEXTBUF;
			framedata = txbuf + XMIT_FP_DATA;
			bufspace = FP_BUF_LEN - XMIT_FP_DATA;
		}
		if (len > 0) {
			bus_space_write_region_1(sc->sc_memt, sc->sc_sramh,
			    framedata, ptr, len);
			size += len;
			bufspace -= len;
			framedata += len;
		}
	}
	if (size % (FP_BUF_LEN - XMIT_FP_DATA)) {
		--sc->sc_xmit_buffers;
		TXB_OUTW(sc, txbuf, XMIT_BUFLEN,
				   (FP_BUF_LEN - XMIT_FP_DATA - bufspace));
	}

	m_freem(m0);		/* free mbuf chain */

	TXB_OUTB(sc, first_txbuf, XMIT_RETCODE, 0xfe);
	TXB_OUTW(sc, first_txbuf, XMIT_FRAMELEN, size);
	TXB_OUTW(sc, first_txbuf, XMIT_LASTBUF, (txbuf + XMIT_NEXTBUF));
	TXB_OUTB(sc, first_txbuf, XMIT_CMD, XMIT_DIR_FRAME);
	TXB_OUTW(sc, first_txbuf, XMIT_STATIONID, 0);
	TXB_OUTB(sc, first_txbuf, XMIT_CMDCORR, sc->sc_xmit_correlator);
	sc->sc_xmit_correlator = (sc->sc_xmit_correlator + 1) & 0x7f;

	/*
	 * To prevent race conditions on 8-bit cards when reading or writing
	 * 16-bit values. See page 4-12 of the IBM manual.
	 */
	TXCA_OUTW(sc, TXCA_FREE_QUEUE_HEAD, 1);
	TXCA_OUTW(sc, TXCA_FREE_QUEUE_HEAD, TXB_INW(sc, txbuf, XMIT_NEXTBUF));

	ACA_SETB(sc, ACA_ISRA_o, XMIT_REQ);

	ifp->if_flags |= IFF_OACTIVE;
	ifp->if_opackets++;
#if 1
/* XXX do while construction */
	goto next;
#endif
}


/*
 *  tr_intr - interrupt handler.  Find the cause of the interrupt and
 *  service it.
 */
int
tr_intr(void *arg)
{
	struct tr_softc *sc = arg;
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
	u_char status;	/* holds status from adapter status register */
	u_char command;	/* holds command from status or request block */
	u_char retcode;	/* holds return value from status or request block */
	int rc = 0;	/* 0 = unclaimed interrupt, 1 = interrupt claimed */

	status = ACA_RDB(sc, ACA_ISRP_o);
	while (status != 0) {

		/* Is this interrupt caused by an adapter check? */
		if (status & ADAP_CHK_INT) {
			printf("%s: adapter check 0x%04x\n",
			    device_xname(sc->sc_dev),
			    (unsigned int)ntohs(ACA_RDW(sc, ACA_WWCR)));

			/* Clear this interrupt bit */
			ACA_RSTB(sc, ACA_ISRP_o, ~(ADAP_CHK_INT));

			rc = 1;		/* Claim interrupt. */
			break;		/* Terminate loop. */
		}
		else if (status & XMIT_COMPLETE) {
			ACA_RSTB(sc, ACA_ISRP_o, ~(XMIT_COMPLETE));
			tr_xint(sc);
			rc = 1;
		}

		/*
		 * Process SRB_RESP_INT, ASB_FREE_INT, ARB_CMD_INT
		 * & SSB_RESP_INT in that order, ISRP-L Hi to Lo
		 */
		else if (status & SRB_RESP_INT) { /* Adapter response in SRB? */
			bus_size_t sap_srb;
			bus_size_t srb;
#ifdef TROPICDEBUG
			bus_size_t log_srb;
#endif
			if (sc->sc_srb == 0)
				sc->sc_srb = ACA_RDW(sc, ACA_WRBR);
			srb = sc->sc_srb; /* pointer to SRB */
			retcode = SRB_INB(sc, srb, SRB_RETCODE);
			command = SRB_INB(sc, srb, SRB_CMD);
			switch (command) {
			case 0x80: /* 0x80 == initialization complete */
			case DIR_CONFIG_FAST_PATH_RAM:
				break;
			case XMIT_DIR_FRAME:	/* Response to xmit request */
			case XMIT_UI_FRM:	/* Response to xmit request */
				/* Response not valid? */
				if (retcode != 0xff)
					aprint_error_dev(sc->sc_dev, "error on xmit request = "
					    "0x%x\n", retcode);
				break;

			case DIR_OPEN_ADAPTER:	/* open-adapter-cmd response */
				/* Open successful? */
				if (retcode == 0) {
					ifp->if_flags |= IFF_UP | IFF_RUNNING;
					/* Save new ACA ctrl block addresses */
					sc->sc_ssb = SRB_INW(sc, srb,
					    SRB_OPENRESP_SSBADDR);
					sc->sc_arb = SRB_INW(sc, srb,
					    SRB_OPENRESP_ARBADDR);
					sc->sc_srb = SRB_INW(sc, srb,
					    SRB_OPENRESP_SRBADDR);
					sc->sc_asb = SRB_INW(sc, srb,
					    SRB_OPENRESP_ASBADDR);

					/*
					 * XXX, what about LLC_{X25,ISO}_LSAP ?
					 * open two more saps .....
					 */
					if (sc->sc_init_status &
					    FAST_PATH_TRANSMIT) {
						sc->sc_xmit_buffers =
						    TXCA_INW(sc,
							TXCA_BUFFER_COUNT);
						sc->sc_nbuf =
						    sc->sc_xmit_buffers;
#ifdef TROPICDEBUG
						printf("%s: %d buffers\n",
						    device_xname(sc->sc_dev),
						    sc->sc_xmit_buffers);
#endif
						sc->sc_xmit_correlator = 0;
						wakeup(&sc->tr_sleepevent);
					}
					else
						tr_opensap(sc, LLC_SNAP_LSAP);
				}
				else {
					aprint_error_dev(sc->sc_dev, "open error = 0x%x\n",
					    SRB_INB(sc, srb, SRB_RETCODE));
					ifp->if_flags &= ~IFF_RUNNING;
					ifp->if_flags &= ~IFF_UP;
/*
 * XXX untimeout depending on the error, timeout in other cases
 * XXX error 0x24 && autospeed mode: open again !!!!
 */
					callout_reset(&sc->sc_init_callout,
					    hz * 30, tr_reopen, sc);
				}
				break;

			case DIR_CLOSE:	/* Response to close adapter command */
				/* Close not successful? */
				if (retcode != 0)
					aprint_error_dev(sc->sc_dev, "close error = 0x%x\n", retcode);
				else {
					ifp->if_flags &= ~IFF_RUNNING;
					ifp->if_flags &= ~IFF_UP;
					ifp->if_flags &= ~IFF_OACTIVE;
					wakeup(&sc->tr_sleepevent);
				}
				break;
			case DIR_SET_DEFAULT_RING_SPEED:
				wakeup(&sc->tr_sleepevent);
				break;

			case DLC_OPEN_SAP:     	/* Response to open sap cmd */
				sap_srb = sc->sc_srb;
				if (SRB_INB(sc, sap_srb, SRB_OPNSAP_SAPVALUE)
				    == LLC_SNAP_LSAP)
					sc->exsap_station =
					    SRB_INW(sc, sap_srb,
					        SRB_OPNSAP_STATIONID);
				printf("%s: Token Ring opened\n",
				    device_xname(sc->sc_dev));
				wakeup(&sc->tr_sleepevent);
				break;
/* XXX DLC_CLOSE_SAP not needed ? */
			case DLC_CLOSE_SAP: /* Response to close sap cmd */
				break;
			case DIR_READ_LOG:   /* Response to read log */
				/* Cmd not successful? */
				if (retcode != 0)
					aprint_error_dev(sc->sc_dev, "read error log cmd err = "
					    "0x%x\n", retcode);
#ifdef TROPICDEBUG
				log_srb = sc->sc_srb;
				printf("%s: ERROR LOG:\n", device_xname(sc->sc_dev));
				printf("%s: Line=%d, Internal=%d, Burst=%d\n",
				    device_xname(sc->sc_dev),
				    (SRB_INB(sc, log_srb, SRB_LOG_LINEERRS)),
				    (SRB_INB(sc, log_srb, SRB_LOG_INTERRS)),
				    (SRB_INB(sc, log_srb, SRB_LOG_BRSTERRS)));
				printf("%s: A/C=%d, Abort=%d, Lost frames=%d\n",
				    device_xname(sc->sc_dev),
				    (SRB_INB(sc, log_srb, SRB_LOG_ACERRS)),
				    (SRB_INB(sc, log_srb, SRB_LOG_ABRTERRS)),
				    (SRB_INB(sc, log_srb, SRB_LOG_LOSTFRMS)));
				printf("%s: Receive congestion=%d, Frame copied=%d, Frequency=%d\n",
				    device_xname(sc->sc_dev),
				    (SRB_INB(sc, log_srb, SRB_LOG_RCVCONG)),
				    (SRB_INB(sc, log_srb, SRB_LOG_FCPYERRS)),
				    (SRB_INB(sc, log_srb, SRB_LOG_FREQERRS)));
				printf("%s: Token=%d\n", device_xname(sc->sc_dev),
				    (SRB_INB(sc, log_srb, SRB_LOG_TOKENERRS)));
#endif /* TROPICDEBUG */
				ifp->if_flags &= ~IFF_OACTIVE;
				break;
			default:
				printf("%s: bad SRB command encountered 0x%x\n",
				    device_xname(sc->sc_dev), command);
				break;
			}
			/* clear the SRB-response interrupt bit */
			ACA_RSTB(sc, ACA_ISRP_o, ~(SRB_RESP_INT));

		}

		else if (status & ASB_FREE_INT) { /* Is ASB Free? */
			bus_size_t asb = sc->sc_asb;

			/*
			 * Remove message from asb queue, first element in
			 * structure is the command. command == REC_DATA?
			 * size = 8 : size = 10
			 * reply in isra_l with (RESP_IN_ASB | ASB_FREE)
			 */
			retcode = ASB_INB(sc, asb, CMD_RETCODE);
			command = ASB_INB(sc, asb, CMD_CMD);
			switch (command) {
			case REC_DATA:		/* Receive */
				/* Response not valid? */
				if (retcode != 0xff)
					aprint_error_dev(sc->sc_dev, "ASB bad receive response = 0x%x\n", retcode);
				break;
			case XMIT_DIR_FRAME:	/* Transmit */
			case XMIT_UI_FRM:   	/* Transmit */
				/* Response not valid? */
				if (retcode != 0xff)
					aprint_error_dev(sc->sc_dev, "ASB response err on xmit = 0x%x\n", retcode);
				break;
			default:
				aprint_error_dev(sc->sc_dev, "invalid command in ASB = 0x%x\n", command);
				break;
			}
			/* Clear this interrupt bit */
			ACA_RSTB(sc, ACA_ISRP_o, ~(ASB_FREE_INT));
		}
		else if (status & ARB_CMD_INT) { /* Command for PC to handle? */
			bus_size_t arb = sc->sc_arb;

			command = ARB_INB(sc, arb, ARB_CMD);
			switch (command) {
			case DLC_STATUS:    /* DLC status change */
				printf("%s: ARB new DLC status = 0x%x\n",
				    device_xname(sc->sc_dev),
				    ARB_INW(sc, arb, ARB_DLCSTAT_STATUS));
				break;
			case REC_DATA:		/* Adapter has data for PC */
				/* Call receive interrupt handler */
				tr_rint(sc);
				break;

			case RING_STAT_CHANGE:	/* Ring status change */
				if (ARB_INW(sc, arb, ARB_RINGSTATUS) &
				    (SIGNAL_LOSS + LOBE_FAULT)){
					aprint_error_dev(sc->sc_dev, "signal loss / lobe fault\n");
					ifp->if_flags &= ~IFF_RUNNING;
					ifp->if_flags &= ~IFF_UP;
					IFQ_PURGE(&ifp->if_snd);
					callout_reset(&sc->sc_reinit_callout,
					    hz * 30, tr_reinit, sc);
				}
				else {
#ifdef TROPICDEBUG
					if (ARB_INW(sc, arb, ARB_RINGSTATUS) &
					    ~(SOFT_ERR))
						printf("%s: ARB new ring status"
						    " = 0x%x\n",
						    device_xname(sc->sc_dev),
						    ARB_INW(sc, arb,
							ARB_RINGSTATUS));
#endif /* TROPICDEBUG */
				}
				if (ARB_INW(sc, arb, ARB_RINGSTATUS) &
				    LOG_OFLOW){
/*
 * XXX CMD_IN_SRB, handle with SRB_FREE_INT ?
 */
					ifp->if_flags |= IFF_OACTIVE;
					SRB_OUTB(sc, sc->sc_srb, SRB_CMD,
					    DIR_READ_LOG);
					/* Read & reset err log cmnd in SRB. */
					ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);
				}
				break;

			case XMIT_DATA_REQ: /* Adapter wants data to transmit */
				/* Call transmit interrupt handler */
				tr_oldxint(sc);
				break;

			default:
				aprint_error_dev(sc->sc_dev, "invalid command in ARB = 0x%x\n", command);
				break;
			}

			/* Clear this interrupt bit */
			ACA_RSTB(sc, ACA_ISRP_o, ~(ARB_CMD_INT));

			/* Tell adapter that ARB is now free */
			ACA_SETB(sc, ACA_ISRA_o, ARB_FREE);
		}


		else if (status & SSB_RESP_INT) {  /* SSB resp. to SRB cmd? */
			bus_size_t	ssb = sc->sc_ssb;

			retcode = SSB_INB(sc, ssb, SSB_RETCODE);
			command = SSB_INB(sc, ssb, SSB_CMD);
			switch (command) {
			case XMIT_UI_FRM:
			case XMIT_DIR_FRAME:  /* SSB response to SRB xmit cmd */
				/* collect status on last packet */
				if (retcode != 0) {
					printf("%s: xmit return code = 0x%x\n",
					    device_xname(sc->sc_dev), retcode);
					/* XXXchb */
					if (retcode == 0x22) {
						printf("%s: FS = 0x%2x\n",
						    device_xname(sc->sc_dev),
						    SSB_INB(sc, ssb,
							SSB_XMITERR));
					}
					ifp->if_oerrors++;
				}
				else
					ifp->if_opackets++;

				ifp->if_flags &= ~IFF_OACTIVE;
/*
 * XXX should this be done here ?
 */
				/* if data on send queue */
				if (IFQ_IS_EMPTY(&ifp->if_snd) == 0)
					tr_oldstart(ifp);
				break;

			case XMIT_XID_CMD:
				printf("tr_int: xmit XID return code = 0x%x\n",
				    retcode);
				break;
			default:
				aprint_error_dev(sc->sc_dev, "SSB error, invalid command =%x\n", command);
			}
			/* clear this interrupt bit */
			ACA_RSTB(sc, ACA_ISRP_o, ~(SSB_RESP_INT));

			/* tell adapter that SSB is available */
			ACA_SETB(sc, ACA_ISRA_o, SSB_FREE);
		}
		rc = 1;		/* Claim responsibility for interrupt */
		status = ACA_RDB(sc, ACA_ISRP_o);
	}
	/* Is this interrupt caused by an adapter error or access violation? */
	if (ACA_RDB(sc, ACA_ISRP_e) & (TCR_INT | ERR_INT | ACCESS_INT)) {
		printf("%s: adapter error, ISRP_e = 0x%x\n",
		    device_xname(sc->sc_dev), ACA_RDB(sc, ACA_ISRP_e));

		/* Clear these interrupt bits */
		ACA_RSTB(sc, ACA_ISRP_e, ~(TCR_INT | ERR_INT | ACCESS_INT));
		rc = 1;		/* Claim responsibility for interrupt */

	}

	/* Clear IRQ latch in order to reenable interrupts. */
	bus_space_write_1(sc->sc_piot, sc->sc_pioh, TR_CLEARINT, 0);
	return (rc);
}

#ifdef notyet
int asb_reply_rcv(void)
{
}

int asb_reply_xmit(void)
{
}

int asb_response(bus_size_t asb, size_t len)
{
	if (empty_queue) {
		answer with RESP_IN_ASB | ASB_FREE
	}
	else {
		put asb in queue
	}
}
#endif


/*
 *  U-B receive interrupt.
 *
 * in the original version, this routine had three tasks:
 *
 *	1. move the data into the receive buffer and set up various pointers
 *	   in the tr_softc struct
 *	2. switch on the type field for ip and arp, dropping all else
 *	3. resetting the adaptor status block info (asb) and updating the
 *	   tr_softc struct
 *		determine lan message type, pull packet off interface and
 *		pass to an appropriate higher-level routine
 *
 */
void
tr_rint(struct tr_softc *sc)
{
	bus_size_t arb = sc->sc_arb;
	bus_size_t asb = sc->sc_asb;
	struct rbcb *rbc = &sc->rbc;
	struct mbuf *m;
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;

#ifdef TROPICDEBUG
	printf("tr_rint: arb.command = %x, arb.station_id= %x\n",
	    ARB_INB(sc, arb, ARB_CMD), ARB_INW(sc, arb, ARB_STATIONID));
	printf("arb.buf_addr = %x, arb.lan_hdr_len = %x\n",
	    ARB_INW(sc, arb, ARB_RXD_BUFADDR),
	    ARB_INB(sc, arb, ARB_RXD_LANHDRLEN));
	printf("arb.dlc_hdr_len = %d, arb.frame_len = %d\n",
	    ARB_INB(sc, arb, ARB_RXD_DLCHDRLEN),
	    ARB_INW(sc, arb, ARB_RXD_FRAMELEN));
	printf("arb.msg_type = %x\n", ARB_INB(sc, arb, ARB_RXD_MSGTYPE));
#endif /* TROPICDEBUG */
	/*
	 * copy the offset in RAM of the first receive buffer from the
	 * receive-data block of the adapter request block associated
	 * with the unit's softc struct into the receive control block.
	 */
	rbc->rbufp = ARB_INW(sc, arb, ARB_RXD_BUFADDR);

	/*
	 * copy the pointer to data in first receive buffer
	 */
	rbc->rbuf_datap = rbc->rbufp + RB_DATA;
	/*
	 * the token-ring header is viewed as two header structs: the physical
	 * header (aka TR header) with access, frame, dest, src, and routing
	 * information, and the logical link control header (aka LLC header)
	 * with dsap, ssap, llc, proto and type fields.
	 *
	 * rfc1042 requires support for unnumbered information (UI) commands,
	 * but does not specify a required semantic, so we'll discard them.
	 *
	 */

	/*
	 * if there is a second receive buffer, set up the next pointer
	 */
	if (RB_INW(sc, rbc->rbufp, RB_NEXTBUF))
		rbc->rbufp_next = RB_INW(sc, rbc->rbufp, RB_NEXTBUF) -
		    RB_NEXTBUF;
	else
		rbc->rbufp_next = 0;	/* we're finished */

	rbc->data_len = RB_INW(sc, rbc->rbufp, RB_BUFLEN);
	/*
	 * At this point we move the packet from the adapter to a chain
	 * of mbufs
	 */
	m = tr_get(sc, ARB_INW(sc, arb, ARB_RXD_FRAMELEN), ifp);
/*
 * XXX Clear ARB interrupt here?
 */
/*
 * XXX create a queue where the responses are buffered
 * XXX but is it really needed ?
 */

	if (ASB_INB(sc, asb, RECV_RETCODE) != 0xff)
		printf("tr_rint: ASB IS NOT FREE!!!\n");
	/*
	 * Load receive response into ASB.
	 */
	ASB_OUTB(sc, asb, RECV_CMD, REC_DATA);
	ASB_OUTW(sc, asb, RECV_STATIONID, ARB_INW(sc, arb, ARB_STATIONID));
	ASB_OUTW(sc, asb, RECV_RESP_RECBUFADDR,
	    ARB_INW(sc, arb, ARB_RXD_BUFADDR));

	if (m == 0) {
		/*
		 * Tell adapter data lost, no mbufs.
		 */
		ASB_OUTB(sc, asb, RECV_RETCODE, 0x20);
		ACA_SETB(sc, ACA_ISRA_o, RESP_IN_ASB);
		++ifp->if_ierrors;
#ifdef TROPICDEBUG
		printf("tr_rint: packet dropped\n");
#endif /* TROPICDEBUG */
	}
	else {
		/*
		 * Indicate successful receive.
		 */
		ASB_OUTB(sc, asb, RECV_RETCODE, 0);
		ACA_SETB(sc, ACA_ISRA_o, RESP_IN_ASB);
		++ifp->if_ipackets;

		bpf_mtap(ifp, m);
		(*ifp->if_input)(ifp, m);
	}
}

/*
 *  Interrupt handler for old style "adapter requires data to transmit".
 */
void
tr_oldxint(struct tr_softc *sc)
{
	bus_size_t arb = sc->sc_arb;	/* pointer to ARB */
	bus_size_t asb = sc->sc_asb;	/* pointer to ASB */
	bus_size_t dhb;			/* pointer to DHB */
	struct mbuf *m0;		/* pointer to top of mbuf chain */
	u_short size = 0;
	char	command;
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
	struct token_header *trh;
	int i;
	u_int8_t hlen;

/*
 * XXX xmit_asb_response()
 */
	if (ASB_INB(sc, asb, XMIT_RETCODE) != 0xff)
		printf("tr_oldxint: ASB IS NOT FREE!!!\n");

	/* load parameters into ASB */
	ASB_OUTB(sc, asb, XMIT_CMDCORR, ARB_INB(sc, arb, ARB_XMT_CMDCORR));
	ASB_OUTW(sc, asb, XMIT_STATIONID, ARB_INW(sc, arb, ARB_STATIONID));
	ASB_OUTB(sc, asb, XMIT_RETCODE, 0);
/*
 * XXX LLC_{X25,ISO}_LSAP
 */
	ASB_OUTB(sc, asb, XMIT_REMSAP, LLC_SNAP_LSAP);

	/* XXX if num_dhb == 2 this should alternate between the two buffers */
	dhb = ARB_INW(sc, arb, ARB_XMT_DHBADDR);

	command = SRB_INB(sc, sc->sc_srb, SRB_CMD);

	if (command == XMIT_XID_CMD || command == XMIT_TEST_CMD) {
		ASB_OUTB(sc, asb, XMIT_CMD, command);
		ASB_OUTW(sc, asb, XMIT_FRAMELEN, 0x11);
/*
 * XXX 0xe == sizeof(struct token_header)
 */
		ASB_OUTB(sc, asb, XMIT_HDRLEN, 0x0e);

		SR_OUTB(sc, (dhb + 0), TOKEN_AC);
		SR_OUTB(sc, (dhb + 1), TOKEN_FC);
		/* Load destination and source addresses. */
		for (i=0; i < ISO88025_ADDR_LEN; i++) {
			SR_OUTB(sc, (dhb + 2 + i), 0xff);
			SR_OUTB(sc, (dhb + 8 + i), 0x00);
		}
	}
	else {
/*
 * XXX what's command here ?  command = 0x0d (always ?)
 */
		/* if data in queue, copy mbuf chain to DHB */
		IFQ_DEQUEUE(&ifp->if_snd, m0);
		if (m0 != 0) {
			bpf_mtap(ifp, m0);
			/* Pull packet off interface send queue, fill DHB. */
			trh = mtod(m0, struct token_header *);
			hlen = sizeof(struct token_header);
			if (trh->token_shost[0] & TOKEN_RI_PRESENT) {
/*
 * XXX assumes route info is in the same mbuf as the token-ring header
 */
				struct token_rif	*rif;

				rif = TOKEN_RIF(trh);
				hlen += ((ntohs(rif->tr_rcf) & TOKEN_RCF_LEN_MASK) >> 8);
			}
			size = tr_mbcopy(sc, dhb, m0);
			m_freem(m0);

			ASB_OUTB(sc, asb, XMIT_CMD, XMIT_UI_FRM);
			ASB_OUTB(sc, asb, XMIT_HDRLEN, hlen);

			/* Set size of transmission frame in ASB. */
			ASB_OUTW(sc, asb, XMIT_FRAMELEN, size);
		}
		else {
			aprint_error_dev(sc->sc_dev, "unexpected empty mbuf send queue\n");

			/* Set size of transmission frame in ASB to zero. */
			ASB_OUTW(sc, asb, XMIT_FRAMELEN, 0);
		}
	}
/*
 * XXX asb_response(void *asb, len)
 */
	/* tell adapter that there is a response in the ASB */
	ACA_SETB(sc, ACA_ISRA_o, RESP_IN_ASB);
}

/*
 *  Interrupt handler for fast path transmit complete
 */
void
tr_xint(struct tr_softc *sc)
{
	u_short	tail;
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
	bus_size_t txbuf;

	/*
	 * To prevent race conditions on 8-bit cards when reading or writing
	 * 16-bit values. See page 4-12 of the IBM manual.
	 * XXX use volatile ?
	 */
	do {
		tail = TXCA_INW(sc, TXCA_COMPLETION_QUEUE_TAIL);
	} while (tail != TXCA_INW(sc, TXCA_COMPLETION_QUEUE_TAIL));
	while (tail != TXCA_INW(sc, TXCA_FREE_QUEUE_TAIL)) {
		txbuf = TXCA_INW(sc, TXCA_FREE_QUEUE_TAIL) - XMIT_NEXTBUF;
		txbuf = TXB_INW(sc, txbuf, XMIT_NEXTBUF) - XMIT_NEXTBUF;
		if (TXB_INB(sc, txbuf, XMIT_RETCODE) != 0) {
			ifp->if_oerrors++;
			aprint_error_dev(sc->sc_dev, "xmit error = 0x%x\n",
			    TXB_INB(sc, txbuf, XMIT_RETCODE));
		}
		sc->sc_xmit_buffers +=
		    (TXB_INW(sc, txbuf, XMIT_FRAMELEN) + 514 - 1) / 514;
		tail = TXB_INW(sc, txbuf, XMIT_LASTBUF);
		TXCA_OUTW(sc, TXCA_FREE_QUEUE_TAIL, tail);
		tail = TXCA_INW(sc, TXCA_COMPLETION_QUEUE_TAIL);
		do {
			tail = TXCA_INW(sc, TXCA_COMPLETION_QUEUE_TAIL);
		} while (tail != TXCA_INW(sc, TXCA_COMPLETION_QUEUE_TAIL));
	}
	if (sc->sc_xmit_buffers == sc->sc_nbuf)
		ifp->if_flags &= ~IFF_OACTIVE;
	tr_start(ifp);
}


/*
 * copy out the packet byte-by-byte in reasonably optimal fashion
 */
int
tr_mbcopy(struct tr_softc *sc, bus_size_t dhb, struct mbuf *m0)
{
	bus_size_t addr = dhb;
	int len, size = 0;
	char *ptr;
	struct mbuf *m;

	for (m = m0; m; m = m->m_next) {
		len = m->m_len;
		ptr = mtod(m, char *);

		bus_space_write_region_1(sc->sc_memt, sc->sc_sramh,
		    addr, ptr, len);
		size += len;
		addr += len;
	}
	return (size);
}

/*
 * Pull read data off an interface.
 * Len is length of data, with local net header stripped.
 * Off is non-zero if a trailer protocol was used, and
 * gives the offset of the trailer information.
 * XXX trailer information, really ????
 * We copy the trailer information and then all the normal
 * data into mbufs.
 *
 * called from tr_rint - receive interrupt routine
 */
struct mbuf *
tr_get(struct tr_softc *sc, int totlen, struct ifnet *ifp)
{
	int len;
	struct mbuf *m, *m0, *newm;

	MGETHDR(m0, M_DONTWAIT, MT_DATA);
	if (m0 == 0)
		return (0);

	m0->m_pkthdr.rcvif = ifp;
	m0->m_pkthdr.len = totlen;
	len = MHLEN;

	m = m0;
	while (totlen > 0) {
		if (totlen >= MINCLSIZE) {
			MCLGET(m, M_DONTWAIT);
			if ((m->m_flags & M_EXT) == 0) {
				m_free(m0);
				return 0;
			}
			len = MCLBYTES;
		}

		/*
		 * Make sure data after the MAC header is aligned.
		 */
		if (m == m0) {
			char *newdata = (char *)
			   ALIGN(m->m_data + sizeof(struct token_header)) -
			   sizeof(struct token_header);
			len -= newdata - m->m_data;
			m->m_data = newdata;
		}
		m->m_len = len = min(totlen, len);
		tr_bcopy(sc, mtod(m, char *), len);
		totlen -= len;
		if (totlen > 0) {
			MGET(newm, M_DONTWAIT, MT_DATA);
			if (newm == 0){
				m_freem(m0);
				return (0);
			}
			m->m_next = newm;
			m = newm;
			len = MLEN;
		}
		/*
		 * ignore trailers case again
		 */
	}
	return (m0);
}

/*
 *  tr_ioctl - process an ioctl request
 */
int
tr_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
	struct tr_softc *sc = ifp->if_softc;
	struct ifreq *ifr = (struct ifreq *) data;
	struct ifaddr *ifa = (struct ifaddr *) data;
	int s;
	int error = 0;

	s = splnet();

	switch (cmd) {
	case SIOCINITIFADDR:
		if ((error = tr_enable(sc)) != 0)
			break;

		/* XXX if not running  */
		if ((ifp->if_flags & IFF_RUNNING) == 0) {
			tr_init(sc);   /* before arp_ifinit/arpwhohas */
			tr_sleep(sc);
		}
		switch (ifa->ifa_addr->sa_family) {
#ifdef INET
		case AF_INET:
			arp_ifinit(ifp, ifa);
			break;
#endif /*INET*/
		default:
			break;
		}
		break;
	case SIOCSIFFLAGS:
		if ((error = ifioctl_common(ifp, cmd, data)) != 0)
			break;
		/*
		 * 1- If the adapter is DOWN , turn the device off
		 *       ie. adapter down but still running
		 * 2- If the adapter is UP, turn the device on
		 *       ie. adapter up but not running yet
		 */
		switch (ifp->if_flags & (IFF_UP|IFF_RUNNING)) {
		case IFF_RUNNING:
			tr_stop(sc);
			ifp->if_flags &= ~IFF_RUNNING;
			tr_disable(sc);
			break;
		case IFF_UP:
			if ((error = tr_enable(sc)) != 0)
				break;
			tr_init(sc);
			tr_sleep(sc);
			break;
		default:
			/*
			 * XXX handle other flag changes
			 */
			break;
		}
		break;
	case SIOCGIFMEDIA:
	case SIOCSIFMEDIA:
		error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd);
		break;
	case SIOCSIFMTU:
		if (ifr->ifr_mtu > sc->sc_maxmtu)
			error = EINVAL;
		else if ((error = ifioctl_common(ifp, cmd, data)) == ENETRESET)
			error = 0;
		break;
	default:
		error = ifioctl_common(ifp, cmd, data);
	}
	splx(s);
	return (error);
}


/*
 *  tr_bcopy - like bcopy except that it knows about the structure of
 *	      adapter receive buffers.
 */
void
tr_bcopy(struct tr_softc *sc, u_char *dest, int len)
	/* sc:	 pointer to softc struct for this adapter */
	/* dest:		 destination address */
	/* len:		 number of bytes to copy */
{
	struct rbcb *rbc = &sc->rbc;	/* pointer to rec buf ctl blk */

	/* While amount of data needed >= amount in current receive buffer. */
	while (len >= rbc->data_len) {
		/* Copy all data from receive buffer to destination. */

		bus_space_read_region_1(sc->sc_memt, sc->sc_sramh,
		    rbc->rbuf_datap, dest, (bus_size_t)rbc->data_len);
		len -= rbc->data_len;	/* update length left to transfer */
		dest += rbc->data_len;	/* update destination address */

		/* Make next receive buffer current receive buffer. */
		rbc->rbufp = rbc->rbufp_next;
		if (rbc->rbufp != 0) { /* More receive buffers? */

			/* Calculate pointer to next receive buffer. */
			rbc->rbufp_next = RB_INW(sc, rbc->rbufp, RB_NEXTBUF);
			if (rbc->rbufp_next != 0)
				rbc->rbufp_next -= RB_NEXTBUF;

			/* Get pointer to data in current receive buffer. */
			rbc->rbuf_datap = rbc->rbufp + RB_DATA;

			/* Get length of data in current receive buffer. */
			rbc->data_len = RB_INW(sc, rbc->rbufp, RB_BUFLEN);
		}
		else {
			if (len != 0)	/* len should equal zero. */
				printf("tr_bcopy: residual data not copied\n");
			return;
		}
	}

	/* Amount of data needed is < amount in current receive buffer. */

	bus_space_read_region_1(sc->sc_memt, sc->sc_sramh,
	    rbc->rbuf_datap, dest, (bus_size_t)len);
	rbc->data_len -= len;	/* Update count of data in receive buffer. */
	rbc->rbuf_datap += len;	/* Update pointer to receive buffer data. */
}

/*
 *  tr_opensap - open the token ring SAP interface
 */
void
tr_opensap(struct tr_softc *sc, u_char type)
{
	bus_size_t srb = sc->sc_srb;

/************************************************************************
 ** To use the SAP level interface, we will have to execute a          **
 ** DLC.OPEN.SAP (pg.6-61 of the Token Ring Tech. Ref.) after we have  **
 ** received a good return code from the DIR.OPEN.ADAPTER command.     **
 ** We will open the IP SAP x'aa'.                                     **
 **                                                                    **
 ** STEPS:                                                             **
 **      1) Reset SRB response interrupt bit                           **
 **      2) Use the open_sap srb.                                      **
 **      3) Fill the following fields:                                 **
 **            command    - x'15'                                      **
 **            sap_value  - x'aa'                                      **
 **            sap_options- x'24'                                      **
 **                                                                    **
 ***********************************************************************/

	ACA_RSTB(sc, ACA_ISRP_o, ~(SRB_RESP_INT));

	SRB_OUTB(sc, srb, SRB_CMD, DLC_OPEN_SAP);
	SRB_OUTB(sc, srb, SRB_RETCODE, 0x00);
	SRB_OUTW(sc, srb, SRB_OPNSAP_STATIONID, 0x0000);
	SRB_OUTB(sc, srb, SRB_OPNSAP_TIMERT1, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_TIMERT2, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_TIMERTI, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_MAXOUT, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_MAXIN, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_MAXOUTINCR, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_MAXRETRY, 0x00);
	SRB_OUTB(sc, srb, SRB_OPNSAP_GSAPMAXMEMB, 0x00);
	SRB_OUTW(sc, srb, SRB_OPNSAP_MAXIFIELD, 0x0088);
	SRB_OUTB(sc, srb, SRB_OPNSAP_SAPVALUE, type);
	SRB_OUTB(sc, srb, SRB_OPNSAP_SAPOPTIONS, 0x24);
	SRB_OUTB(sc, srb, SRB_OPNSAP_STATIONCNT, 0x01);
	SRB_OUTB(sc, srb, SRB_OPNSAP_SAPGSAPMEMB, 0x00);

	ACA_SETB(sc, ACA_ISRP_e, INT_ENABLE);
	ACA_SETB(sc, ACA_ISRA_o, CMD_IN_SRB);
}

/*
 *  tr_sleep - sleep to wait for adapter to open
 */
void
tr_sleep(struct tr_softc *sc)
{
	int error;

	error = tsleep(&sc->tr_sleepevent, 1, "trsleep", hz * 30);
	if (error == EWOULDBLOCK)
		printf("%s: sleep event timeout\n", device_xname(sc->sc_dev));
}

void
tr_watchdog(struct ifnet *ifp)
{
	struct tr_softc	*sc = ifp->if_softc;

	log(LOG_ERR,"%s: device timeout\n", device_xname(sc->sc_dev));
	++ifp->if_oerrors;

	tr_reset(sc);
}

int
tr_enable(struct tr_softc *sc)
{
	if (sc->sc_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_enabled = 1;
	return (0);
}

void
tr_disable(struct tr_softc *sc)
{
	if (sc->sc_enabled != 0 && sc->sc_disable != NULL) {
		(*sc->sc_disable)(sc);
		sc->sc_enabled = 0;
	}
}

int
tr_activate(device_t self, enum devact act)
{
	struct tr_softc *sc = device_private(self);

	switch (act) {
	case DVACT_DEACTIVATE:
		if_deactivate(&sc->sc_ethercom.ec_if);
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

int
tr_detach(device_t self, int flags)
{
	struct tr_softc *sc = device_private(self);
	struct ifnet *ifp = &sc->sc_ethercom.ec_if;

	tr_disable(sc);

	callout_stop(&sc->sc_init_callout);
	callout_stop(&sc->sc_reinit_callout);

	/* Delete all remaining media. */
	ifmedia_delete_instance(&sc->sc_media, IFM_INST_ANY);

	token_ifdetach(ifp);
	if_detach(ifp);

	shutdownhook_disestablish(sc->sc_sdhook);

	return (0);
}
