aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/patm/if_patm_tx.c
diff options
context:
space:
mode:
authorHartmut Brandt <harti@FreeBSD.org>2003-07-15 11:57:24 +0000
committerHartmut Brandt <harti@FreeBSD.org>2003-07-15 11:57:24 +0000
commit8dd4275c365932c7651942ebea2a3f54d68d7ff1 (patch)
tree44442f21b1cfa8eb3a2a600b9254c9f26a4d4a39 /sys/dev/patm/if_patm_tx.c
parentb3d4fe3d0432f8f8f59b521ff9cd2173dacfd427 (diff)
downloadsrc-8dd4275c365932c7651942ebea2a3f54d68d7ff1.tar.gz
src-8dd4275c365932c7651942ebea2a3f54d68d7ff1.zip
This is a driver for IDT77252 based ATM interfaces. It has been tested
with a ProATM-155 and an IDT evaluation board and should also work with a ProATM-25 (it seems to work at least, I cannot really measure what the card emits). The driver has been tested on i386 and sparc64, but should work an other archs also. It supports UBR, CBR, ABR and VBR; AAL0, AAL5 and AALraw. As an additional feature VCI/VPI 0/0 can be opened for receiving in AALraw mode and receives all cells not claimed by other open VCs (even cells with invalid GFC, VPI and VCI fields and OAM cells). Thanks to Christian Bucari from ProSum for lending two cards and answering my questions.
Notes
Notes: svn path=/head/; revision=117632
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 000000000000..0d460182233a
--- /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 ("");
+}