summaryrefslogtreecommitdiffstats
path: root/sys/dev/patm/if_patm_tx.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/patm/if_patm_tx.c')
-rw-r--r--sys/dev/patm/if_patm_tx.c1273
1 files changed, 1273 insertions, 0 deletions
diff --git a/sys/dev/patm/if_patm_tx.c b/sys/dev/patm/if_patm_tx.c
new file mode 100644
index 0000000..0d46018
--- /dev/null
+++ b/sys/dev/patm/if_patm_tx.c
@@ -0,0 +1,1273 @@
+/*
+ * 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$");
+
+#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 + sc->ifatm.mib.pcr - 1) / sc->ifatm.mib.pcr));
+}
+
+static __inline u_int
+slots2cr(struct patm_softc *sc, u_int slots)
+{
+ return ((slots * sc->ifatm.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 > sc->ifatm.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 > sc->ifatm.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 = (struct patm_softc *)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_flags & IFF_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->ifatm.ifnet.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->ifatm.ifnet.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->ifatm.ifnet.if_oerrors++;
+ continue;
+ }
+ cid = PATM_CID(sc, vpi, vci);
+ vcc = sc->vccs[cid];
+ if (vcc == NULL) {
+ m_freem(m);
+ sc->ifatm.ifnet.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->ifatm.ifnet.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->ifatm.ifnet.if_oerrors++;
+ m_freem(m);
+ continue;
+ }
+ break;
+
+ case PATM_RAW_NOHEC:
+ if (m->m_pkthdr.len != 52) {
+ sc->ifatm.ifnet.if_oerrors++;
+ m_freem(m);
+ continue;
+ }
+ break;
+
+ case PATM_RAW_CS:
+ if (m->m_pkthdr.len != 64) {
+ sc->ifatm.ifnet.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->ifatm.ifnet.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.flags & ATM_PH_AAL5) &&
+ (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->ifatm.ifnet.if_oerrors++;
+ return (NULL);
+ }
+ if (plen % 48 == 0)
+ return (m0);
+ }
+ pad = 48 - plen % 48;
+ 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->ifatm.ifnet.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->ifatm.ifnet.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->ifatm.ifnet.if_oerrors++;
+ SLIST_INSERT_HEAD(&sc->tx_maps_free, map, link);
+ m_freem(m);
+ continue;
+ }
+
+ sc->ifatm.ifnet.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->vflags & PATM_VCC_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 = sc->ifatm.mib.pcr * 32 /
+ ((1 << (acri >> 10)) * (acri & 0x3ff));
+
+ if (cps != vcc->cps) {
+ /* send message */
+ patm_debug(sc, VCC, "ACRI=%04x CPS=%u", acri, 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, sc->ifatm.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 ("");
+}
OpenPOWER on IntegriCloud