/*-
 * Copyright (c) 2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 * 	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 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.
 *
 * The TST allocation algorithm is from the IDT driver which is:
 *
 *	Copyright (c) 2000, 2001 Richard Hodges and Matriplex, inc.
 *	All rights reserved.
 *
 *	Copyright (c) 1996, 1997, 1998, 1999 Mark Tinguely
 *	All rights reserved.
 *
 * Author: Hartmut Brandt <harti@freebsd.org>
 *
 * Driver for IDT77252 based cards like ProSum's.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/patm/if_patm_tx.c,v 1.10 2005/08/09 10:19:51 rwatson Exp $");

#include "opt_inet.h"
#include "opt_natm.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <sys/queue.h>
#include <sys/condvar.h>
#include <sys/endian.h>
#include <vm/uma.h>

#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_media.h>
#include <net/if_atm.h>
#include <net/route.h>
#ifdef ENABLE_BPF
#include <net/bpf.h>
#endif
#include <netinet/in.h>
#include <netinet/if_atm.h>

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

#include <dev/utopia/utopia.h>
#include <dev/patm/idt77252reg.h>
#include <dev/patm/if_patmvar.h>

static struct mbuf *patm_tx_pad(struct patm_softc *sc, struct mbuf *m0);
static void patm_launch(struct patm_softc *sc, struct patm_scd *scd);

static struct patm_txmap *patm_txmap_get(struct patm_softc *);
static void patm_load_txbuf(void *, bus_dma_segment_t *, int,
    bus_size_t, int);

static void patm_tst_alloc(struct patm_softc *sc, struct patm_vcc *vcc);
static void patm_tst_free(struct patm_softc *sc, struct patm_vcc *vcc);
static void patm_tst_timer(void *p);
static void patm_tst_update(struct patm_softc *);

static void patm_tct_start(struct patm_softc *sc, struct patm_vcc *);

static const char *dump_scd(struct patm_softc *sc, struct patm_scd *scd)
    __unused;
static void patm_tct_print(struct patm_softc *sc, u_int cid) __unused;

/*
 * Structure for communication with the loader function for transmission
 */
struct txarg {
	struct patm_softc *sc;
	struct patm_scd	*scd;		/* scheduling channel */
	struct patm_vcc	*vcc;		/* the VCC of this PDU */
	struct mbuf	*mbuf;
	u_int		hdr;		/* cell header */
};

static __inline u_int
cbr2slots(struct patm_softc *sc, struct patm_vcc *vcc)
{
	/* compute the number of slots we need, make sure to get at least
	 * the specified PCR */
	return ((u_int)(((uint64_t)(sc->mmap->tst_size - 1) *
	    vcc->vcc.tparam.pcr + IFP2IFATM(sc->ifp)->mib.pcr - 1) / IFP2IFATM(sc->ifp)->mib.pcr));
}

static __inline u_int
slots2cr(struct patm_softc *sc, u_int slots)
{
	return ((slots * IFP2IFATM(sc->ifp)->mib.pcr + sc->mmap->tst_size - 2) /
	    (sc->mmap->tst_size - 1));
}

/* check if we can open this one */
int
patm_tx_vcc_can_open(struct patm_softc *sc, struct patm_vcc *vcc)
{

	/* check resources */
	switch (vcc->vcc.traffic) {

	  case ATMIO_TRAFFIC_CBR:
	    {
		u_int slots = cbr2slots(sc, vcc);

		if (slots > sc->tst_free + sc->tst_reserve)
			return (EINVAL);
		break;
	    }

	  case ATMIO_TRAFFIC_VBR:
		if (vcc->vcc.tparam.scr > sc->bwrem)
			return (EINVAL);
		if (vcc->vcc.tparam.pcr > IFP2IFATM(sc->ifp)->mib.pcr)
			return (EINVAL);
		if (vcc->vcc.tparam.scr > vcc->vcc.tparam.pcr ||
		    vcc->vcc.tparam.mbs == 0)
			return (EINVAL);
		break;

	  case ATMIO_TRAFFIC_ABR:
		if (vcc->vcc.tparam.tbe == 0 ||
		    vcc->vcc.tparam.nrm == 0)
			/* needed to compute CRM */
			return (EINVAL);
		if (vcc->vcc.tparam.pcr > IFP2IFATM(sc->ifp)->mib.pcr ||
		    vcc->vcc.tparam.icr > vcc->vcc.tparam.pcr ||
		    vcc->vcc.tparam.mcr > vcc->vcc.tparam.icr)
			return (EINVAL);
		if (vcc->vcc.tparam.mcr > sc->bwrem ||
		    vcc->vcc.tparam.icr > sc->bwrem)
			return (EINVAL);
		break;
	}

	return (0);
}

#define	NEXT_TAG(T) do {				\
	(T) = ((T) + 1) % IDT_TSQE_TAG_SPACE;		\
    } while (0)

/*
 * open it
 */
void
patm_tx_vcc_open(struct patm_softc *sc, struct patm_vcc *vcc)
{
	struct patm_scd *scd;

	if (vcc->vcc.traffic == ATMIO_TRAFFIC_UBR) {
		/* we use UBR0 */
		vcc->scd = sc->scd0;
		vcc->vflags |= PATM_VCC_TX_OPEN;
		return;
	}

	/* get an SCD */
	scd = patm_scd_alloc(sc);
	if (scd == NULL) {
		/* should not happen */
		patm_printf(sc, "out of SCDs\n");
		return;
	}
	vcc->scd = scd;
	patm_scd_setup(sc, scd);
	patm_tct_setup(sc, scd, vcc);

	if (vcc->vcc.traffic != ATMIO_TRAFFIC_CBR)
		patm_tct_start(sc, vcc);

	vcc->vflags |= PATM_VCC_TX_OPEN;
}

/*
 * close the given vcc for transmission
 */
void
patm_tx_vcc_close(struct patm_softc *sc, struct patm_vcc *vcc)
{
	struct patm_scd *scd;
	struct mbuf *m;

	vcc->vflags |= PATM_VCC_TX_CLOSING;

	if (vcc->vcc.traffic == ATMIO_TRAFFIC_UBR) {
		/* let the queue PDUs go out */
		vcc->scd = NULL;
		vcc->vflags &= ~(PATM_VCC_TX_OPEN | PATM_VCC_TX_CLOSING);
		return;
	}
	scd = vcc->scd;

	/* empty the waitq */
	for (;;) {
		_IF_DEQUEUE(&scd->q, m);
		if (m == NULL)
			break;
		m_freem(m);
	}

	if (scd->num_on_card == 0) {
		/* we are idle */
		vcc->vflags &= ~PATM_VCC_TX_OPEN;

		if (vcc->vcc.traffic == ATMIO_TRAFFIC_CBR)
			patm_tst_free(sc, vcc);

		patm_sram_write4(sc, scd->sram + 0, 0, 0, 0, 0);
		patm_sram_write4(sc, scd->sram + 4, 0, 0, 0, 0);
		patm_scd_free(sc, scd);

		vcc->scd = NULL;
		vcc->vflags &= ~PATM_VCC_TX_CLOSING;

		return;
	}

	/* speed up transmission */
	patm_nor_write(sc, IDT_NOR_TCMDQ, IDT_TCMDQ_UIER(vcc->cid, 0xff));
	patm_nor_write(sc, IDT_NOR_TCMDQ, IDT_TCMDQ_ULACR(vcc->cid, 0xff));

	/* wait for the interrupt to drop the number to 0 */
	patm_debug(sc, VCC, "%u buffers still on card", scd->num_on_card);
}

/* transmission side finally closed */
void
patm_tx_vcc_closed(struct patm_softc *sc, struct patm_vcc *vcc)
{

	patm_debug(sc, VCC, "%u.%u TX closed", vcc->vcc.vpi, vcc->vcc.vci);

	if (vcc->vcc.traffic == ATMIO_TRAFFIC_VBR)
		sc->bwrem += vcc->vcc.tparam.scr;
}

/*
 * Pull off packets from the interface queue and try to transmit them.
 * If the transmission fails because of a full transmit channel, we drop
 * packets for CBR and queue them for other channels up to limit.
 * This limit should depend on the CDVT for VBR and ABR, but it doesn't.
 */
void
patm_start(struct ifnet *ifp)
{
	struct patm_softc *sc = ifp->if_softc;
	struct mbuf *m;
	struct atm_pseudohdr *aph;
	u_int vpi, vci, cid;
	struct patm_vcc *vcc;

	mtx_lock(&sc->mtx);
	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
		mtx_unlock(&sc->mtx);
		return;
	}

	while (1) {
		/* get a new mbuf */
		IF_DEQUEUE(&ifp->if_snd, m);
		if (m == NULL)
			break;

		/* split of pseudo header */
		if (m->m_len < sizeof(*aph) &&
		    (m = m_pullup(m, sizeof(*aph))) == NULL) {
			sc->ifp->if_oerrors++;
			continue;
		}

		aph = mtod(m, struct atm_pseudohdr *);
		vci = ATM_PH_VCI(aph);
		vpi = ATM_PH_VPI(aph);
		m_adj(m, sizeof(*aph));

		/* reject empty packets */
		if (m->m_pkthdr.len == 0) {
			m_freem(m);
			sc->ifp->if_oerrors++;
			continue;
		}

		/* check whether this is a legal vcc */
		if (!LEGAL_VPI(sc, vpi) || !LEGAL_VCI(sc, vci) || vci == 0) {
			m_freem(m);
			sc->ifp->if_oerrors++;
			continue;
		}
		cid = PATM_CID(sc, vpi, vci);
		vcc = sc->vccs[cid];
		if (vcc == NULL) {
			m_freem(m);
			sc->ifp->if_oerrors++;
			continue;
		}

		/* must be multiple of 48 if not AAL5 */
		if (vcc->vcc.aal == ATMIO_AAL_0 ||
		    vcc->vcc.aal == ATMIO_AAL_34) {
			/* XXX AAL3/4 format? */
			if (m->m_pkthdr.len % 48 != 0 &&
			    (m = patm_tx_pad(sc, m)) == NULL) {
				sc->ifp->if_oerrors++;
				continue;
			}
		} else if (vcc->vcc.aal == ATMIO_AAL_RAW) {
			switch (vcc->vflags & PATM_RAW_FORMAT) {

			  default:
			  case PATM_RAW_CELL:
				if (m->m_pkthdr.len != 53) {
					sc->ifp->if_oerrors++;
					m_freem(m);
					continue;
				}
				break;

			  case PATM_RAW_NOHEC:
				if (m->m_pkthdr.len != 52) {
					sc->ifp->if_oerrors++;
					m_freem(m);
					continue;
				}
				break;

			  case PATM_RAW_CS:
				if (m->m_pkthdr.len != 64) {
					sc->ifp->if_oerrors++;
					m_freem(m);
					continue;
				}
				break;
			}
		}

		/* save data */
		m->m_pkthdr.header = vcc;

		/* try to put it on the channels queue */
		if (_IF_QFULL(&vcc->scd->q)) {
			sc->ifp->if_oerrors++;
			sc->stats.tx_qfull++;
			m_freem(m);
			continue;
		}
		_IF_ENQUEUE(&vcc->scd->q, m);

#ifdef ENABLE_BPF
		if (!(vcc->vcc.flags & ATMIO_FLAG_NG) &&
		    (vcc->vcc.aal == ATMIO_AAL_5) &&
		    (vcc->vcc.flags & ATM_PH_LLCSNAP))
		 	BPF_MTAP(ifp, m);
#endif

		/* kick the channel to life */
		patm_launch(sc, vcc->scd);

	}
	mtx_unlock(&sc->mtx);
}

/*
 * Pad non-AAL5 packet to a multiple of 48-byte.
 * We assume AAL0 only. We have still to decide on the format of AAL3/4.
 */
static struct mbuf *
patm_tx_pad(struct patm_softc *sc, struct mbuf *m0)
{
	struct mbuf *last, *m;
	u_int plen, pad, space;

	plen = m_length(m0, &last);
	if (plen != m0->m_pkthdr.len) {
		patm_printf(sc, "%s: mbuf length mismatch %d %u\n", __func__,
		    m0->m_pkthdr.len, plen);
		m0->m_pkthdr.len = plen;
		if (plen == 0) {
			m_freem(m0);
			sc->ifp->if_oerrors++;
			return (NULL);
		}
		if (plen % 48 == 0)
			return (m0);
	}
	pad = 48 - plen % 48;
	m0->m_pkthdr.len += pad;
	if (M_WRITABLE(last)) {
		if (M_TRAILINGSPACE(last) >= pad) {
			bzero(last->m_data + last->m_len, pad);
			last->m_len += pad;
			return (m0);
		}
		space = M_LEADINGSPACE(last);
		if (space + M_TRAILINGSPACE(last) >= pad) {
			bcopy(last->m_data, last->m_data + space, last->m_len);
			last->m_data -= space;
			bzero(last->m_data + last->m_len, pad);
			last->m_len += pad;
			return (m0);
		}
	}
	MGET(m, M_DONTWAIT, MT_DATA);
	if (m == 0) {
		m_freem(m0);
		sc->ifp->if_oerrors++;
		return (NULL);
	}
	bzero(mtod(m, u_char *), pad);
	m->m_len = pad;
	last->m_next = m;

	return (m0);
}

/*
 * Try to put as many packets from the channels queue onto the channel
 */
static void
patm_launch(struct patm_softc *sc, struct patm_scd *scd)
{
	struct txarg a;
	struct mbuf *m, *tmp;
	u_int segs;
	struct patm_txmap *map;
	int error;

	a.sc = sc;
	a.scd = scd;

	/* limit the number of outstanding packets to the tag space */
	while (scd->num_on_card < IDT_TSQE_TAG_SPACE) {
		/* get the next packet */
		_IF_DEQUEUE(&scd->q, m);
		if (m == NULL)
			break;

		a.vcc = m->m_pkthdr.header;

		/* we must know the number of segments beforehand - count
		 * this may actually give a wrong number of segments for
		 * AAL_RAW where we still need to remove the cell header */
		segs = 0;
		for (tmp = m; tmp != NULL; tmp = tmp->m_next)
			if (tmp->m_len != 0)
				segs++;

		/* check whether there is space in the queue */
		if (segs >= scd->space) {
			/* put back */
			_IF_PREPEND(&scd->q, m);
			sc->stats.tx_out_of_tbds++;
			break;
		}

		/* get a DMA map */
		if ((map = patm_txmap_get(sc)) == NULL) {
			_IF_PREPEND(&scd->q, m);
			sc->stats.tx_out_of_maps++;
			break;
		}

		/* load the map */
		m->m_pkthdr.header = map;
		a.mbuf = m;

		/* handle AAL_RAW */
		if (a.vcc->vcc.aal == ATMIO_AAL_RAW) {
			u_char hdr[4];

			m_copydata(m, 0, 4, hdr);
			a.hdr = (hdr[0] << 24) | (hdr[1] << 16) |
			    (hdr[2] << 8) | hdr[3];

			switch (a.vcc->vflags & PATM_RAW_FORMAT) {

			  default:
			  case PATM_RAW_CELL:
				m_adj(m, 5);
				break;

			  case PATM_RAW_NOHEC:
				m_adj(m, 4);
				break;

			  case PATM_RAW_CS:
				m_adj(m, 16);
				break;
			}
		} else
			a.hdr = IDT_TBD_HDR(a.vcc->vcc.vpi, a.vcc->vcc.vci,
			    0, 0);

		error = bus_dmamap_load_mbuf(sc->tx_tag, map->map, m,
		    patm_load_txbuf, &a, BUS_DMA_NOWAIT);
		if (error == EFBIG) {
			if ((m = m_defrag(m, M_DONTWAIT)) == NULL) {
				sc->ifp->if_oerrors++;
				continue;
			}
			error = bus_dmamap_load_mbuf(sc->tx_tag, map->map, m,
			    patm_load_txbuf, &a, BUS_DMA_NOWAIT);
		}
		if (error != 0) {
			sc->stats.tx_load_err++;
			sc->ifp->if_oerrors++;
			SLIST_INSERT_HEAD(&sc->tx_maps_free, map, link);
			m_freem(m);
			continue;
		}

		sc->ifp->if_opackets++;
	}
}

/*
 * Load the DMA segments into the scheduling channel
 */
static void
patm_load_txbuf(void *uarg, bus_dma_segment_t *segs, int nseg,
    bus_size_t mapsize, int error)
{
	struct txarg *a= uarg;
	struct patm_scd *scd = a->scd;
	u_int w1, w3, cnt;
	struct idt_tbd *tbd = NULL;
	u_int rest = mapsize;

	if (error != 0)
		return;

	cnt = 0;
	while (nseg > 0) {
		if (segs->ds_len == 0) {
			/* transmit buffer length must be > 0 */
			nseg--;
			segs++;
			continue;
		}
		/* rest after this buffer */
		rest -= segs->ds_len;

		/* put together status word */
		w1 = 0;
		if (rest < 48 /* && a->vcc->vcc.aal != ATMIO_AAL_5 */)
			/* last cell is in this buffer */
			w1 |= IDT_TBD_EPDU;

		if (a->vcc->vcc.aal == ATMIO_AAL_5)
			w1 |= IDT_TBD_AAL5;
		else if (a->vcc->vcc.aal == ATMIO_AAL_34)
			w1 |= IDT_TBD_AAL34;
		else
			w1 |= IDT_TBD_AAL0;

		w1 |= segs->ds_len;

		/* AAL5 PDU length (unpadded) */
		if (a->vcc->vcc.aal == ATMIO_AAL_5)
			w3 = mapsize;
		else
			w3 = 0;

		if (rest == 0)
			w1 |= IDT_TBD_TSIF | IDT_TBD_GTSI |
			    (scd->tag << IDT_TBD_TAG_SHIFT);

		tbd = &scd->scq[scd->tail];

		tbd->flags = htole32(w1);
		tbd->addr = htole32(segs->ds_addr);
		tbd->aal5 = htole32(w3);
		tbd->hdr = htole32(a->hdr);

		patm_debug(a->sc, TX, "TBD(%u): %08x %08x %08x %08x",
		    scd->tail, w1, segs->ds_addr, w3, a->hdr);

		/* got to next entry */
		if (++scd->tail == IDT_SCQ_SIZE)
			scd->tail = 0;
		cnt++;
		nseg--;
		segs++;
	}
	scd->space -= cnt;
	scd->num_on_card++;

	KASSERT(rest == 0, ("bad mbuf"));
	KASSERT(cnt > 0, ("no segs"));
	KASSERT(scd->space > 0, ("scq full"));

	KASSERT(scd->on_card[scd->tag] == NULL,
	    ("scd on_card wedged %u%s", scd->tag, dump_scd(a->sc, scd)));
	scd->on_card[scd->tag] = a->mbuf;
	a->mbuf->m_pkthdr.csum_data = cnt;

	NEXT_TAG(scd->tag);

	patm_debug(a->sc, TX, "SCD tail %u (%lx:%lx)", scd->tail,
	    (u_long)scd->phy, (u_long)scd->phy + (scd->tail << IDT_TBD_SHIFT));
	patm_sram_write(a->sc, scd->sram,
	    scd->phy + (scd->tail << IDT_TBD_SHIFT));

	if (patm_sram_read(a->sc, a->vcc->cid * 8 + 3) & IDT_TCT_IDLE) {
		/*
		 * if the connection is idle start it. We cannot rely
		 * on a flag set by patm_tx_idle() here, because sometimes
		 * the card seems to place an idle TSI into the TSQ but
		 * forgets to raise an interrupt.
		 */
		patm_nor_write(a->sc, IDT_NOR_TCMDQ,
		    IDT_TCMDQ_START(a->vcc->cid));
	}
}

/*
 * packet transmitted
 */
void
patm_tx(struct patm_softc *sc, u_int stamp, u_int status)
{
	u_int cid, tag, last;
	struct mbuf *m;
	struct patm_vcc *vcc;
	struct patm_scd *scd;
	struct patm_txmap *map;

	/* get the connection */
	cid = PATM_CID(sc, IDT_TBD_VPI(status), IDT_TBD_VCI(status));
	if ((vcc = sc->vccs[cid]) == NULL) {
		/* closed UBR connection */
		return;
	}
	scd = vcc->scd;

	tag = IDT_TSQE_TAG(stamp);

	last = scd->last_tag;
	if (tag == last) {
		patm_printf(sc, "same tag %u\n", tag);
		return;
	}

	/* Errata 12 requests us to free all entries up to the one
	 * with the given tag. */
	do {
		/* next tag to try */
		NEXT_TAG(last);

		m = scd->on_card[last];
		KASSERT(m != NULL, ("%stag=%u", dump_scd(sc, scd), tag));
		scd->on_card[last] = NULL;
		patm_debug(sc, TX, "ok tag=%x", last);

		map = m->m_pkthdr.header;
		scd->space += m->m_pkthdr.csum_data;

		bus_dmamap_sync(sc->tx_tag, map->map,
		    BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(sc->tx_tag, map->map);
		m_freem(m);
		SLIST_INSERT_HEAD(&sc->tx_maps_free, map, link);
		scd->num_on_card--;

		if (vcc->vflags & PATM_VCC_TX_CLOSING) {
			if (scd->num_on_card == 0) {
				/* done with this VCC */
				if (vcc->vcc.traffic == ATMIO_TRAFFIC_CBR)
					patm_tst_free(sc, vcc);

				patm_sram_write4(sc, scd->sram + 0, 0, 0, 0, 0);
				patm_sram_write4(sc, scd->sram + 4, 0, 0, 0, 0);
				patm_scd_free(sc, scd);

				vcc->scd = NULL;
				vcc->vflags &= ~PATM_VCC_TX_CLOSING;

				if (vcc->vcc.flags & ATMIO_FLAG_ASYNC) {
					patm_tx_vcc_closed(sc, vcc);
					if (!(vcc->vflags & PATM_VCC_OPEN))
						patm_vcc_closed(sc, vcc);
				} else
					cv_signal(&sc->vcc_cv);
				return;
			}
			patm_debug(sc, VCC, "%u buffers still on card",
			    scd->num_on_card);

			if (vcc->vcc.traffic == ATMIO_TRAFFIC_ABR) {
				/* insist on speeding up transmission for ABR */
				patm_nor_write(sc, IDT_NOR_TCMDQ,
				    IDT_TCMDQ_UIER(vcc->cid, 0xff));
				patm_nor_write(sc, IDT_NOR_TCMDQ,
				    IDT_TCMDQ_ULACR(vcc->cid, 0xff));
			}
		}

	} while (last != tag);
	scd->last_tag = tag;

	if (vcc->vcc.traffic == ATMIO_TRAFFIC_ABR) {
		u_int acri, cps;

		acri = (patm_sram_read(sc, 8 * cid + 2) >> IDT_TCT_ACRI_SHIFT)
		    & 0x3fff;
		cps = IFP2IFATM(sc->ifp)->mib.pcr * 32 /
		    ((1 << (acri >> 10)) * (acri & 0x3ff));

		if (cps != vcc->cps) {
			patm_debug(sc, VCC, "ACRI=%04x CPS=%u", acri, cps);
			ATMEV_SEND_ACR_CHANGED(IFP2IFATM(sc->ifp), vcc->vcc.vpi,
			    vcc->vcc.vci, cps);
			vcc->cps = cps;
		}
	}

	patm_launch(sc, scd);
}

/*
 * VBR/ABR connection went idle
 * Either restart it or set the idle flag.
 */
void
patm_tx_idle(struct patm_softc *sc, u_int cid)
{
	struct patm_vcc *vcc;

	patm_debug(sc, VCC, "idle %u", cid);

	if ((vcc = sc->vccs[cid]) != NULL &&
	    (vcc->vflags & (PATM_VCC_TX_OPEN | PATM_VCC_TX_CLOSING)) != 0 &&
	    vcc->scd != NULL && (vcc->scd->num_on_card != 0 ||
	    _IF_QLEN(&vcc->scd->q) != 0)) {
		/*
		 * If there is any packet outstanding in the SCD re-activate
		 * the channel and kick it.
		 */
		patm_nor_write(sc, IDT_NOR_TCMDQ,
		    IDT_TCMDQ_START(vcc->cid));

		patm_launch(sc, vcc->scd);
	}
}

/*
 * Convert a (24bit) rate to the atm-forum form
 * Our rate is never larger than 19 bit.
 */
static u_int
cps2atmf(u_int cps)
{
	u_int e;

	if (cps == 0)
		return (0);
	cps <<= 9;
	e = 0;
	while (cps > (1024 - 1)) {
		e++;
		cps >>= 1;
	}
	return ((1 << 14) | (e << 9) | (cps & 0x1ff));
}

/*
 * Do a binary search on the log2rate table to convert the rate
 * to its log form. This assumes that the ATM-Forum form is monotonically
 * increasing with the plain cell rate.
 */
static u_int
rate2log(struct patm_softc *sc, u_int rate)
{
	const uint32_t *tbl;
	u_int lower, upper, mid, done, val, afr;

	afr = cps2atmf(rate);

	if (sc->flags & PATM_25M)
		tbl = patm_rtables25;
	else
		tbl = patm_rtables155;

	lower = 0;
	upper = 255;
	done = 0;
	while (!done) {
		mid = (lower + upper) / 2;
		val = tbl[mid] >> 17;
		if (val == afr || upper == lower)
			break;
		if (afr > val)
			lower = mid + 1;
		else
			upper = mid - 1;
	}
	if (val > afr && mid > 0)
		mid--;
	return (mid);
}

/*
 * Return the table index for an increase table. The increase table
 * must be selected not by the RIF itself, but by PCR/2^RIF. Each table
 * represents an additive increase of a cell rate that can be computed
 * from the first table entry (the value in this entry will not be clamped
 * by the link rate).
 */
static u_int
get_air_table(struct patm_softc *sc, u_int rif, u_int pcr)
{
	const uint32_t *tbl;
	u_int increase, base, lair0, ret, t, cps;

#define	GET_ENTRY(TAB, IDX) (0xffff & ((IDX & 1) ?			\
	(tbl[512 + (IDX / 2) + 128 * (TAB)] >> 16) :			\
	(tbl[512 + (IDX / 2) + 128 * (TAB)])))

#define	MANT_BITS	10
#define	FRAC_BITS	16

#define	DIFF_TO_FP(D)	(((D) & ((1 << MANT_BITS) - 1)) << ((D) >> MANT_BITS))
#define	AFR_TO_INT(A)	((1 << (((A) >> 9) & 0x1f)) * \
			    (512 + ((A) & 0x1ff)) / 512 * ((A) >> 14))

	if (sc->flags & PATM_25M)
		tbl = patm_rtables25;
	else
		tbl = patm_rtables155;
	if (rif >= patm_rtables_ntab)
		rif = patm_rtables_ntab - 1;
	increase = pcr >> rif;

	ret = 0;
	for (t = 0; t < patm_rtables_ntab; t++) {
		/* get base rate of this table */
		base = GET_ENTRY(t, 0);
		/* convert this to fixed point */
		lair0 = DIFF_TO_FP(base) >> FRAC_BITS;

		/* get the CPS from the log2rate table */
		cps = AFR_TO_INT(tbl[lair0] >> 17) - 10;

		if (increase >= cps)
			break;

		ret = t;
	}
	return (ret + 4);
}

/*
 * Setup the TCT
 */
void
patm_tct_setup(struct patm_softc *sc, struct patm_scd *scd,
    struct patm_vcc *vcc)
{
	uint32_t tct[8];
	u_int sram;
	u_int mbs, token;
	u_int tmp, crm, rdf, cdf, air, mcr;

	bzero(tct, sizeof(tct));
	if (vcc == NULL) {
		/* special case for UBR0 */
		sram = 0;
		tct[0] = IDT_TCT_UBR | scd->sram;
		tct[7] = IDT_TCT_UBR_FLG;

	} else {
		sram = vcc->cid * 8;
		switch (vcc->vcc.traffic) {

		  case ATMIO_TRAFFIC_CBR:
			patm_tst_alloc(sc, vcc);
			tct[0] = IDT_TCT_CBR | scd->sram;
			/* must account for what was really allocated */
			break;

		  case ATMIO_TRAFFIC_VBR:
			/* compute parameters for the TCT */
			scd->init_er = rate2log(sc, vcc->vcc.tparam.pcr);
			scd->lacr = rate2log(sc, vcc->vcc.tparam.scr);

			/* get the 16-bit fraction of SCR/PCR
			 * both a 24 bit. Do it the simple way. */
			token = (uint64_t)(vcc->vcc.tparam.scr << 16) /
			    vcc->vcc.tparam.pcr;

			patm_debug(sc, VCC, "VBR: init_er=%u lacr=%u "
			    "token=0x%04x\n", scd->init_er, scd->lacr, token);

			tct[0] = IDT_TCT_VBR | scd->sram;
			tct[2] = IDT_TCT_TSIF;
			tct[3] = IDT_TCT_IDLE | IDT_TCT_HALT;
			tct[4] = IDT_TCT_MAXIDLE;
			tct[5] = 0x01000000;
			if ((mbs = vcc->vcc.tparam.mbs) > 0xff)
				mbs = 0xff;
			tct[6] = (mbs << 16) | token;
			sc->bwrem -= vcc->vcc.tparam.scr;
			break;

		  case ATMIO_TRAFFIC_ABR:
			scd->init_er = rate2log(sc, vcc->vcc.tparam.pcr);
			scd->lacr = rate2log(sc, vcc->vcc.tparam.icr);
			mcr = rate2log(sc, vcc->vcc.tparam.mcr);

			/* compute CRM */
			tmp = vcc->vcc.tparam.tbe / vcc->vcc.tparam.nrm;
			if (tmp * vcc->vcc.tparam.nrm < vcc->vcc.tparam.tbe)
				tmp++;
			for (crm = 1; tmp > (1 << crm); crm++)
				;
			if (crm > 0x7)
				crm = 7;

			air = get_air_table(sc, vcc->vcc.tparam.rif,
			    vcc->vcc.tparam.pcr);

			if ((rdf = vcc->vcc.tparam.rdf) >= patm_rtables_ntab)
				rdf = patm_rtables_ntab - 1;
			rdf += patm_rtables_ntab + 4;

			if ((cdf = vcc->vcc.tparam.cdf) >= patm_rtables_ntab)
				cdf = patm_rtables_ntab - 1;
			cdf += patm_rtables_ntab + 4;

			patm_debug(sc, VCC, "ABR: init_er=%u lacr=%u mcr=%u "
			    "crm=%u air=%u rdf=%u cdf=%u\n", scd->init_er,
			    scd->lacr, mcr, crm, air, rdf, cdf);

			tct[0] = IDT_TCT_ABR | scd->sram;
			tct[1] = crm << IDT_TCT_CRM_SHIFT;
			tct[3] = IDT_TCT_HALT | IDT_TCT_IDLE |
			    (4 << IDT_TCT_NAGE_SHIFT);
			tct[4] = mcr << IDT_TCT_LMCR_SHIFT;
			tct[5] = (cdf << IDT_TCT_CDF_SHIFT) |
			    (rdf << IDT_TCT_RDF_SHIFT) |
			    (air << IDT_TCT_AIR_SHIFT);

			sc->bwrem -= vcc->vcc.tparam.mcr;
			break;
		}
	}

	patm_sram_write4(sc, sram + 0, tct[0], tct[1], tct[2], tct[3]);
	patm_sram_write4(sc, sram + 4, tct[4], tct[5], tct[6], tct[7]);

	patm_debug(sc, VCC, "TCT[%u]: %08x %08x %08x %08x  %08x %08x %08x %08x",
	    sram / 8, patm_sram_read(sc, sram + 0),
	    patm_sram_read(sc, sram + 1), patm_sram_read(sc, sram + 2),
	    patm_sram_read(sc, sram + 3), patm_sram_read(sc, sram + 4),
	    patm_sram_read(sc, sram + 5), patm_sram_read(sc, sram + 6),
	    patm_sram_read(sc, sram + 7));
}

/*
 * Start a channel
 */
static void
patm_tct_start(struct patm_softc *sc, struct patm_vcc *vcc)
{

	patm_nor_write(sc, IDT_NOR_TCMDQ, IDT_TCMDQ_UIER(vcc->cid,
	    vcc->scd->init_er));
	patm_nor_write(sc, IDT_NOR_TCMDQ, IDT_TCMDQ_SLACR(vcc->cid,
	    vcc->scd->lacr));
}

static void
patm_tct_print(struct patm_softc *sc, u_int cid)
{
#ifdef PATM_DEBUG
	u_int sram = cid * 8;
#endif

	patm_debug(sc, VCC, "TCT[%u]: %08x %08x %08x %08x  %08x %08x %08x %08x",
	    sram / 8, patm_sram_read(sc, sram + 0),
	    patm_sram_read(sc, sram + 1), patm_sram_read(sc, sram + 2),
	    patm_sram_read(sc, sram + 3), patm_sram_read(sc, sram + 4),
	    patm_sram_read(sc, sram + 5), patm_sram_read(sc, sram + 6),
	    patm_sram_read(sc, sram + 7));
}

/*
 * Setup the SCD
 */
void
patm_scd_setup(struct patm_softc *sc, struct patm_scd *scd)
{
	patm_sram_write4(sc, scd->sram + 0,
	    scd->phy, 0, 0xffffffff, 0);
	patm_sram_write4(sc, scd->sram + 4,
	    0, 0, 0, 0);

	patm_debug(sc, VCC, "SCD(%x): %08x %08x %08x %08x %08x %08x %08x %08x",
	    scd->sram,
	    patm_sram_read(sc, scd->sram + 0),
	    patm_sram_read(sc, scd->sram + 1),
	    patm_sram_read(sc, scd->sram + 2),
	    patm_sram_read(sc, scd->sram + 3),
	    patm_sram_read(sc, scd->sram + 4),
	    patm_sram_read(sc, scd->sram + 5),
	    patm_sram_read(sc, scd->sram + 6),
	    patm_sram_read(sc, scd->sram + 7));
}

/*
 * Grow the TX map table if possible
 */
static void
patm_txmaps_grow(struct patm_softc *sc)
{
	u_int i;
	struct patm_txmap *map;
	int err;

	if (sc->tx_nmaps >= sc->tx_maxmaps)
		return;

	for (i = sc->tx_nmaps; i < sc->tx_nmaps + PATM_CFG_TXMAPS_STEP; i++) {
		map = uma_zalloc(sc->tx_mapzone, M_NOWAIT);
		err = bus_dmamap_create(sc->tx_tag, 0, &map->map);
		if (err) {
			uma_zfree(sc->tx_mapzone, map);
			break;
		}
		SLIST_INSERT_HEAD(&sc->tx_maps_free, map, link);
	}

	sc->tx_nmaps = i;
}

/*
 * Allocate a transmission map
 */
static struct patm_txmap *
patm_txmap_get(struct patm_softc *sc)
{
	struct patm_txmap *map;

	if ((map = SLIST_FIRST(&sc->tx_maps_free)) == NULL) {
		patm_txmaps_grow(sc);
		if ((map = SLIST_FIRST(&sc->tx_maps_free)) == NULL)
			return (NULL);
	}
	SLIST_REMOVE_HEAD(&sc->tx_maps_free, link);
	return (map);
}

/*
 * Look whether we are in the process of updating the TST on the chip.
 * If we are set the flag that we need another update.
 * If we are not start the update.
 */
static __inline void
patm_tst_start(struct patm_softc *sc)
{

	if (!(sc->tst_state & TST_PENDING)) {
		sc->tst_state |= TST_PENDING;
		if (!(sc->tst_state & TST_WAIT)) {
			/* timer not running */
			patm_tst_update(sc);
		}
	}
}

/*
 * Allocate TST entries to a CBR connection
 */
static void
patm_tst_alloc(struct patm_softc *sc, struct patm_vcc *vcc)
{
	u_int slots;
	u_int qptr, pptr;
	u_int qmax, pmax;
	u_int pspc, last;

	mtx_lock(&sc->tst_lock);

	/* compute the number of slots we need, make sure to get at least
	 * the specified PCR */
	slots = cbr2slots(sc, vcc);
	vcc->scd->slots = slots;
	sc->bwrem -= slots2cr(sc, slots);

	patm_debug(sc, TST, "tst_alloc: cbr=%u link=%u tst=%u slots=%u",
	    vcc->vcc.tparam.pcr, IFP2IFATM(sc->ifp)->mib.pcr, sc->mmap->tst_size, slots);

	qmax = sc->mmap->tst_size - 1;
	pmax = qmax << 8;

	pspc = pmax / slots;

	pptr = pspc >> 1;	/* starting point */
	qptr = pptr >> 8;

	last = qptr;

	while (slots > 0) {
		if (qptr >= qmax)
			qptr -= qmax;
		if (sc->tst_soft[qptr] != IDT_TST_VBR) {
			/* used - try next */
			qptr++;
			continue;
		}
		patm_debug(sc, TST, "slot[%u] = %u.%u diff=%d", qptr,
		    vcc->vcc.vpi, vcc->vcc.vci, (int)qptr - (int)last);
		last = qptr;

		sc->tst_soft[qptr] = IDT_TST_CBR | vcc->cid | TST_BOTH;
		sc->tst_free--;

		if ((pptr += pspc) >= pmax)
			pptr -= pmax;
		qptr = pptr >> 8;

		slots--;
	}
	patm_tst_start(sc);
	mtx_unlock(&sc->tst_lock);
}

/*
 * Free a CBR connection's TST entries
 */
static void
patm_tst_free(struct patm_softc *sc, struct patm_vcc *vcc)
{
	u_int i;

	mtx_lock(&sc->tst_lock);
	for (i = 0; i < sc->mmap->tst_size - 1; i++) {
		if ((sc->tst_soft[i] & IDT_TST_MASK) == vcc->cid) {
			sc->tst_soft[i] = IDT_TST_VBR | TST_BOTH;
			sc->tst_free++;
		}
	}
	sc->bwrem += slots2cr(sc, vcc->scd->slots);
	patm_tst_start(sc);
	mtx_unlock(&sc->tst_lock);
}

/*
 * Write the soft TST into the idle incore TST and start the wait timer.
 * We assume that we hold the tst lock.
 */
static void
patm_tst_update(struct patm_softc *sc)
{
	u_int flag;		/* flag to clear from soft TST */
	u_int idle;		/* the idle TST */
	u_int act;		/* the active TST */
	u_int i;

	if (sc->tst_state & TST_ACT1) {
		act = 1;
		idle = 0;
		flag = TST_CH0;
	} else {
		act = 0;
		idle = 1;
		flag = TST_CH1;
	}
	/* update the idle one */
	for (i = 0; i < sc->mmap->tst_size - 1; i++)
		if (sc->tst_soft[i] & flag) {
			patm_sram_write(sc, sc->tst_base[idle] + i,
			    sc->tst_soft[i] & ~TST_BOTH);
			sc->tst_soft[i] &= ~flag;
		}
	/* the used one jump to the idle one */
	patm_sram_write(sc, sc->tst_jump[act],
	    IDT_TST_BR | (sc->tst_base[idle] << 2));

	/* wait for the chip to jump */
	sc->tst_state &= ~TST_PENDING;
	sc->tst_state |= TST_WAIT;

	callout_reset(&sc->tst_callout, 1, patm_tst_timer, sc);
}

/*
 * Timer for TST updates
 */
static void
patm_tst_timer(void *p)
{
	struct patm_softc *sc = p;
	u_int act;	/* active TST */
	u_int now;	/* current place in TST */

	mtx_lock(&sc->tst_lock);

	if (sc->tst_state & TST_WAIT) {
		/* ignore the PENDING state while we are waiting for
		 * the chip to switch tables. Once the switch is done,
		 * we will again lock at PENDING */
		act = (sc->tst_state & TST_ACT1) ? 1 : 0;
		now = patm_nor_read(sc, IDT_NOR_NOW) >> 2;
		if (now >= sc->tst_base[act] && now <= sc->tst_jump[act]) {
			/* not yet */
			callout_reset(&sc->tst_callout, 1, patm_tst_timer, sc);
			goto done;
		}
		sc->tst_state &= ~TST_WAIT;
		/* change back jump */
		patm_sram_write(sc, sc->tst_jump[act],
		    IDT_TST_BR | (sc->tst_base[act] << 2));

		/* switch */
		sc->tst_state ^= TST_ACT1;
	}

	if (sc->tst_state & TST_PENDING)
		/* we got another update request while the timer was running. */
		patm_tst_update(sc);

  done:
	mtx_unlock(&sc->tst_lock);
}

static const char *
dump_scd(struct patm_softc *sc, struct patm_scd *scd)
{
	u_int i;

	for (i = 0; i < IDT_TSQE_TAG_SPACE; i++)
		printf("on_card[%u] = %p\n", i, scd->on_card[i]);
	printf("space=%u tag=%u num_on_card=%u last_tag=%u\n",
	    scd->space, scd->tag, scd->num_on_card, scd->last_tag);

	return ("");
}
