/* * Copyright (c) 2001-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. * * Author: Hartmut Brandt * * ForeHE driver. * * Transmission. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_natm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_BPF #include #endif #include #include #include #include #include #include #include #include #include #include #include #include /* * Allocate a new TPD, zero the TPD part. Cannot return NULL if * flag is 0. The TPD is removed from the free list and its used * bit is set. */ static struct tpd * hatm_alloc_tpd(struct hatm_softc *sc, u_int flags) { struct tpd *t; /* if we allocate a transmit TPD check for the reserve */ if (flags & M_NOWAIT) { if (sc->tpd_nfree <= HE_CONFIG_TPD_RESERVE) return (NULL); } else { if (sc->tpd_nfree == 0) return (NULL); } /* make it beeing used */ t = SLIST_FIRST(&sc->tpd_free); KASSERT(t != NULL, ("tpd botch")); SLIST_REMOVE_HEAD(&sc->tpd_free, link); TPD_SET_USED(sc, t->no); sc->tpd_nfree--; /* initialize */ t->mbuf = NULL; t->cid = 0; bzero(&t->tpd, sizeof(t->tpd)); t->tpd.addr = t->no << HE_REGS_TPD_ADDR; return (t); } /* * Free a TPD. If the mbuf pointer in that TPD is not zero, it is assumed, that * the DMA map of this TPD was used to load this mbuf. The map is unloaded * and the mbuf is freed. The TPD is put back onto the free list and * its used bit is cleared. */ static void hatm_free_tpd(struct hatm_softc *sc, struct tpd *tpd) { if (tpd->mbuf != NULL) { bus_dmamap_unload(sc->tx_tag, tpd->map); m_freem(tpd->mbuf); tpd->mbuf = NULL; } /* insert TPD into free list */ SLIST_INSERT_HEAD(&sc->tpd_free, tpd, link); TPD_CLR_USED(sc, tpd->no); sc->tpd_nfree++; } /* * Queue a number of TPD. If there is not enough space none of the TPDs * is queued and an error code is returned. */ static int hatm_queue_tpds(struct hatm_softc *sc, u_int count, struct tpd **list, u_int cid) { u_int space; u_int i; if (count >= sc->tpdrq.size) { sc->istats.tdprq_full++; return (EBUSY); } if (sc->tpdrq.tail < sc->tpdrq.head) space = sc->tpdrq.head - sc->tpdrq.tail; else space = sc->tpdrq.head - sc->tpdrq.tail + sc->tpdrq.size; if (space <= count) { sc->tpdrq.head = (READ4(sc, HE_REGO_TPDRQ_H) >> HE_REGS_TPDRQ_H_H) & (sc->tpdrq.size - 1); if (sc->tpdrq.tail < sc->tpdrq.head) space = sc->tpdrq.head - sc->tpdrq.tail; else space = sc->tpdrq.head - sc->tpdrq.tail + sc->tpdrq.size; if (space <= count) { if_printf(&sc->ifatm.ifnet, "TPDRQ full\n"); sc->istats.tdprq_full++; return (EBUSY); } } /* we are going to write to the TPD queue space */ bus_dmamap_sync(sc->tpdrq.mem.tag, sc->tpdrq.mem.map, BUS_DMASYNC_PREWRITE); /* put the entries into the TPD space */ for (i = 0; i < count; i++) { /* we are going to 'write' the TPD to the device */ bus_dmamap_sync(sc->tpds.tag, sc->tpds.map, BUS_DMASYNC_PREWRITE); sc->tpdrq.tpdrq[sc->tpdrq.tail].tpd = sc->tpds.paddr + HE_TPD_SIZE * list[i]->no; sc->tpdrq.tpdrq[sc->tpdrq.tail].cid = cid; if (++sc->tpdrq.tail == sc->tpdrq.size) sc->tpdrq.tail = 0; } /* update tail pointer */ WRITE4(sc, HE_REGO_TPDRQ_T, (sc->tpdrq.tail << HE_REGS_TPDRQ_T_T)); return (0); } /* * Helper struct for communication with the DMA load helper. */ struct load_txbuf_arg { struct hatm_softc *sc; struct tpd *first; struct mbuf *mbuf; struct hevcc *vcc; int error; u_int pti; u_int vpi, vci; }; /* * Loader callback for the mbuf. This function allocates the TPDs and * fills them. It puts the dmamap and and the mbuf pointer into the last * TPD and then tries to queue all the TPDs. If anything fails, all TPDs * allocated by this function are freed and the error flag is set in the * argument structure. The first TPD must then be freed by the caller. */ static void hatm_load_txbuf(void *uarg, bus_dma_segment_t *segs, int nseg, bus_size_t mapsize, int error) { struct load_txbuf_arg *arg = uarg; u_int tpds_needed, i, n, tpd_cnt; int need_intr; struct tpd *tpd; struct tpd *tpd_list[HE_CONFIG_MAX_TPD_PER_PACKET]; if (error != 0) { DBG(arg->sc, DMA, ("%s -- error=%d plen=%d\n", __func__, error, arg->mbuf->m_pkthdr.len)); return; } /* ensure, we have enough TPDs (remember, we already have one) */ tpds_needed = (nseg + 2) / 3; if (HE_CONFIG_TPD_RESERVE + tpds_needed - 1 > arg->sc->tpd_nfree) { if_printf(&arg->sc->ifatm.ifnet, "%s -- out of TPDs (need %d, " "have %u)\n", __func__, tpds_needed - 1, arg->sc->tpd_nfree + 1); arg->error = 1; return; } /* * Check for the maximum number of TPDs on the connection. */ need_intr = 0; if (arg->sc->max_tpd > 0) { if (arg->vcc->ntpds + tpds_needed > arg->sc->max_tpd) { arg->sc->istats.flow_closed++; arg->vcc->vflags |= HE_VCC_FLOW_CTRL; #ifdef notyet atm_message(&arg->sc->ifatm.ifnet, ATM_MSG_FLOW_CONTROL, (1 << 24) | (arg->vpi << 16) | arg->vci); #endif arg->error = 1; return; } if (arg->vcc->ntpds + tpds_needed > (9 * arg->sc->max_tpd) / 10) need_intr = 1; } tpd = arg->first; tpd_cnt = 0; tpd_list[tpd_cnt++] = tpd; for (i = n = 0; i < nseg; i++, n++) { if (n == 3) { if ((tpd = hatm_alloc_tpd(arg->sc, M_NOWAIT)) == NULL) /* may not fail (see check above) */ panic("%s: out of TPDs", __func__); tpd->cid = arg->first->cid; tpd->tpd.addr |= arg->pti; tpd_list[tpd_cnt++] = tpd; n = 0; } KASSERT(segs[i].ds_addr <= 0xffffffffLU, ("phys addr too large %lx", (u_long)segs[i].ds_addr)); DBG(arg->sc, DMA, ("DMA loaded: %lx/%lu", (u_long)segs[i].ds_addr, (u_long)segs[i].ds_len)); tpd->tpd.bufs[n].addr = segs[i].ds_addr; tpd->tpd.bufs[n].len = segs[i].ds_len; DBG(arg->sc, TX, ("seg[%u]=tpd[%u,%u]=%x/%u", i, tpd_cnt, n, tpd->tpd.bufs[n].addr, tpd->tpd.bufs[n].len)); if (i == nseg - 1) tpd->tpd.bufs[n].len |= HE_REGM_TPD_LST; } /* * Swap the MAP in the first and the last TPD and set the mbuf * pointer into the last TPD. We use the map in the last TPD, because * the map must stay valid until the last TPD is processed by the card. */ if (tpd_cnt > 1) { bus_dmamap_t tmp; tmp = arg->first->map; arg->first->map = tpd_list[tpd_cnt - 1]->map; tpd_list[tpd_cnt - 1]->map = tmp; } tpd_list[tpd_cnt - 1]->mbuf = arg->mbuf; if (need_intr) tpd_list[tpd_cnt - 1]->tpd.addr |= HE_REGM_TPD_INTR; /* queue the TPDs */ if (hatm_queue_tpds(arg->sc, tpd_cnt, tpd_list, arg->first->cid)) { /* free all, except the first TPD */ for (i = 1; i < tpd_cnt; i++) hatm_free_tpd(arg->sc, tpd_list[i]); arg->error = 1; return; } arg->vcc->ntpds += tpd_cnt; } /* * Start output on the interface * * For raw aal we process only the first cell in the mbuf chain! XXX */ void hatm_start(struct ifnet *ifp) { struct hatm_softc *sc = (struct hatm_softc *)ifp->if_softc; struct mbuf *m; struct atm_pseudohdr *aph; u_int cid; struct tpd *tpd; struct load_txbuf_arg arg; u_int len; int error; if (!(ifp->if_flags & IFF_RUNNING)) return; mtx_lock(&sc->mtx); arg.sc = sc; while (1) { IF_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if (m->m_len < sizeof(*aph)) if ((m = m_pullup(m, sizeof(*aph))) == NULL) continue; aph = mtod(m, struct atm_pseudohdr *); arg.vci = ATM_PH_VCI(aph); arg.vpi = ATM_PH_VPI(aph); m_adj(m, sizeof(*aph)); if ((len = m->m_pkthdr.len) == 0) { m_freem(m); continue; } if ((arg.vpi & ~HE_VPI_MASK) || (arg.vci & ~HE_VCI_MASK) || (arg.vci == 0)) { m_freem(m); continue; } cid = HE_CID(arg.vpi, arg.vci); arg.vcc = sc->vccs[cid]; if (arg.vcc == NULL || !(arg.vcc->vflags & HE_VCC_OPEN)) { m_freem(m); continue; } if (arg.vcc->vflags & HE_VCC_FLOW_CTRL) { m_freem(m); sc->istats.flow_drop++; continue; } arg.pti = 0; if (arg.vcc->param.aal == ATMIO_AAL_RAW) { if (len < 52) { m_freem(m); continue; } if (len > 52) { m_adj(m, -((int)(len - 52))); len = 52; } if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) continue; /* ignore header except payload type and CLP */ arg.pti = mtod(m, u_char *)[3] & 0xf; arg.pti = ((arg.pti & 0xe) << 2) | ((arg.pti & 1) << 1); m_adj(m, 4); len -= 4; } #ifdef ENABLE_BPF if (!(arg.vcc->param.flags & ATMIO_FLAG_NG) && (arg.vcc->param.flags & ATM_PH_AAL5) && (arg.vcc->param.flags & ATM_PH_LLCSNAP)) BPF_MTAP(ifp, m); #endif /* Now load a DMA map with the packet. Allocate the first * TPD to get a map. Additional TPDs may be allocated by the * callback. */ if ((tpd = hatm_alloc_tpd(sc, M_NOWAIT)) == NULL) { m_freem(m); sc->ifatm.ifnet.if_oerrors++; continue; } tpd->cid = cid; tpd->tpd.addr |= arg.pti; arg.first = tpd; arg.error = 0; arg.mbuf = m; error = bus_dmamap_load_mbuf(sc->tx_tag, tpd->map, m, hatm_load_txbuf, &arg, 0); if (error == EFBIG) { /* try to defragment the packet */ sc->istats.defrag++; m = m_defrag(m, M_DONTWAIT); if (m == NULL) { sc->ifatm.ifnet.if_oerrors++; continue; } arg.mbuf = m; error = bus_dmamap_load_mbuf(sc->tx_tag, tpd->map, m, hatm_load_txbuf, &arg, 0); } if (error != 0) { if_printf(&sc->ifatm.ifnet, "mbuf loaded error=%d\n", error); hatm_free_tpd(sc, tpd); sc->ifatm.ifnet.if_oerrors++; continue; } if (arg.error) { hatm_free_tpd(sc, tpd); sc->ifatm.ifnet.if_oerrors++; continue; } arg.vcc->opackets++; arg.vcc->obytes += len; sc->ifatm.ifnet.if_opackets++; } mtx_unlock(&sc->mtx); } void hatm_tx_complete(struct hatm_softc *sc, struct tpd *tpd, uint32_t flags) { struct hevcc *vcc = sc->vccs[tpd->cid]; DBG(sc, TX, ("tx_complete cid=%#x flags=%#x", tpd->cid, flags)); if (vcc == NULL) return; if ((flags & HE_REGM_TBRQ_EOS) && (vcc->vflags & HE_VCC_TX_CLOSING)) { vcc->vflags &= ~HE_VCC_TX_CLOSING; if (vcc->vflags & HE_VCC_ASYNC) { hatm_tx_vcc_closed(sc, tpd->cid); if (!(vcc->vflags & HE_VCC_OPEN)) { hatm_vcc_closed(sc, tpd->cid); vcc = NULL; } } else cv_signal(&sc->vcc_cv); } hatm_free_tpd(sc, tpd); if (vcc == NULL) return; vcc->ntpds--; if ((vcc->vflags & HE_VCC_FLOW_CTRL) && vcc->ntpds <= HE_CONFIG_TPD_FLOW_ENB) { vcc->vflags &= ~HE_VCC_FLOW_CTRL; #ifdef notyet atm_message(&sc->ifatm.ifnet, ATM_MSG_FLOW_CONTROL, (0 << 24) | (HE_VPI(tpd->cid) << 16) | HE_VCI(tpd->cid)); #endif } } /* * Convert CPS to Rate for a rate group */ static u_int cps_to_rate(struct hatm_softc *sc, uint32_t cps) { u_int clk = sc->he622 ? HE_622_CLOCK : HE_155_CLOCK; u_int period, rate; /* how many double ticks between two cells */ period = (clk + 2 * cps - 1) / (2 * cps); rate = hatm_cps2atmf(period); if (hatm_atmf2cps(rate) < period) rate++; return (rate); } /* * Check whether the VCC is really closed on the hardware and available for * open. Check that we have enough resources. If this function returns ok, * a later actual open must succeed. Assume, that we are locked between this * function and the next one, so that nothing does change. For CBR this * assigns the rate group and set the rate group's parameter. */ int hatm_tx_vcc_can_open(struct hatm_softc *sc, u_int cid, struct hevcc *vcc) { uint32_t v, line_rate; u_int rc, idx, free_idx; struct atmio_tparam *t = &vcc->param.tparam; /* verify that connection is closed */ #if 0 v = READ_TSR(sc, cid, 4); if(!(v & HE_REGM_TSR4_SESS_END)) { if_printf(&sc->ifatm.ifnet, "cid=%#x not closed (TSR4)\n", cid); return (EBUSY); } #endif v = READ_TSR(sc, cid, 0); if((v & HE_REGM_TSR0_CONN_STATE) != 0) { if_printf(&sc->ifatm.ifnet, "cid=%#x not closed (TSR0=%#x)\n", cid, v); return (EBUSY); } /* check traffic parameters */ line_rate = sc->he622 ? ATM_RATE_622M : ATM_RATE_155M; switch (vcc->param.traffic) { case ATMIO_TRAFFIC_UBR: if (t->pcr == 0 || t->pcr > line_rate) t->pcr = line_rate; if (t->mcr != 0 || t->icr != 0 || t->tbe != 0 || t->nrm != 0 || t->trm != 0 || t->adtf != 0 || t->rif != 0 || t->rdf != 0 || t->cdf != 0) return (EINVAL); break; case ATMIO_TRAFFIC_CBR: /* * Compute rate group index */ if (t->pcr < 10) t->pcr = 10; if (sc->cbr_bw + t->pcr > line_rate) return (EINVAL); if (t->mcr != 0 || t->icr != 0 || t->tbe != 0 || t->nrm != 0 || t->trm != 0 || t->adtf != 0 || t->rif != 0 || t->rdf != 0 || t->cdf != 0) return (EINVAL); rc = cps_to_rate(sc, t->pcr); free_idx = HE_REGN_CS_STPER; for (idx = 0; idx < HE_REGN_CS_STPER; idx++) { if (sc->rate_ctrl[idx].refcnt == 0) { if (free_idx == HE_REGN_CS_STPER) free_idx = idx; } else { if (sc->rate_ctrl[idx].rate == rc) break; } } if (idx == HE_REGN_CS_STPER) { if ((idx = free_idx) == HE_REGN_CS_STPER) return (EBUSY); sc->rate_ctrl[idx].rate = rc; WRITE_MBOX4(sc, HE_REGO_CS_STPER(idx), rc); } vcc->rc = idx; break; case ATMIO_TRAFFIC_ABR: if (t->pcr > line_rate) t->pcr = line_rate; if (t->mcr > line_rate) t->mcr = line_rate; if (t->icr > line_rate) t->icr = line_rate; if (t->tbe == 0 || t->tbe >= 1 << 24 || t->nrm > 7 || t->trm > 7 || t->adtf >= 1 << 10 || t->rif > 15 || t->rdf > 15 || t->cdf > 7) return (EINVAL); break; default: return (EINVAL); } return (0); } #define NRM_CODE2VAL(CODE) (2 * (1 << (CODE))) /* * Actually open the transmit VCC */ void hatm_tx_vcc_open(struct hatm_softc *sc, u_int cid) { struct hevcc *vcc = sc->vccs[cid]; uint32_t tsr0, tsr4, atmf, crm; const struct atmio_tparam *t = &vcc->param.tparam; if (vcc->param.aal == ATMIO_AAL_5) { tsr0 = HE_REGM_TSR0_AAL_5 << HE_REGS_TSR0_AAL; tsr4 = HE_REGM_TSR4_AAL_5 << HE_REGS_TSR4_AAL; } else { tsr0 = HE_REGM_TSR0_AAL_0 << HE_REGS_TSR0_AAL; tsr4 = HE_REGM_TSR4_AAL_0 << HE_REGS_TSR4_AAL; } tsr4 |= 1; switch (vcc->param.traffic) { case ATMIO_TRAFFIC_UBR: atmf = hatm_cps2atmf(t->pcr); tsr0 |= HE_REGM_TSR0_TRAFFIC_UBR << HE_REGS_TSR0_TRAFFIC; tsr0 |= HE_REGM_TSR0_USE_WMIN | HE_REGM_TSR0_UPDATE_GER; WRITE_TSR(sc, cid, 0, 0xf, tsr0); WRITE_TSR(sc, cid, 4, 0xf, tsr4); WRITE_TSR(sc, cid, 1, 0xf, (atmf << HE_REGS_TSR1_PCR)); WRITE_TSR(sc, cid, 2, 0xf, (atmf << HE_REGS_TSR2_ACR)); WRITE_TSR(sc, cid, 9, 0xf, HE_REGM_TSR9_INIT); WRITE_TSR(sc, cid, 3, 0xf, 0); WRITE_TSR(sc, cid, 5, 0xf, 0); WRITE_TSR(sc, cid, 6, 0xf, 0); WRITE_TSR(sc, cid, 7, 0xf, 0); WRITE_TSR(sc, cid, 8, 0xf, 0); WRITE_TSR(sc, cid, 10, 0xf, 0); WRITE_TSR(sc, cid, 11, 0xf, 0); WRITE_TSR(sc, cid, 12, 0xf, 0); WRITE_TSR(sc, cid, 13, 0xf, 0); WRITE_TSR(sc, cid, 14, 0xf, 0); break; case ATMIO_TRAFFIC_CBR: atmf = hatm_cps2atmf(t->pcr); sc->rate_ctrl[vcc->rc].refcnt++; tsr0 |= HE_REGM_TSR0_TRAFFIC_CBR << HE_REGS_TSR0_TRAFFIC; tsr0 |= vcc->rc; WRITE_TSR(sc, cid, 1, 0xf, (atmf << HE_REGS_TSR1_PCR)); WRITE_TSR(sc, cid, 2, 0xf, (atmf << HE_REGS_TSR2_ACR)); WRITE_TSR(sc, cid, 3, 0xf, 0); WRITE_TSR(sc, cid, 5, 0xf, 0); WRITE_TSR(sc, cid, 6, 0xf, 0); WRITE_TSR(sc, cid, 7, 0xf, 0); WRITE_TSR(sc, cid, 8, 0xf, 0); WRITE_TSR(sc, cid, 10, 0xf, 0); WRITE_TSR(sc, cid, 11, 0xf, 0); WRITE_TSR(sc, cid, 12, 0xf, 0); WRITE_TSR(sc, cid, 13, 0xf, 0); WRITE_TSR(sc, cid, 14, 0xf, 0); WRITE_TSR(sc, cid, 4, 0xf, tsr4); WRITE_TSR(sc, cid, 9, 0xf, HE_REGM_TSR9_INIT); WRITE_TSR(sc, cid, 0, 0xf, tsr0); sc->cbr_bw += t->pcr; break; case ATMIO_TRAFFIC_ABR: if ((crm = t->tbe / NRM_CODE2VAL(t->nrm)) > 0xffff) crm = 0xffff; tsr0 |= HE_REGM_TSR0_TRAFFIC_ABR << HE_REGS_TSR0_TRAFFIC; tsr0 |= HE_REGM_TSR0_USE_WMIN | HE_REGM_TSR0_UPDATE_GER; WRITE_TSR(sc, cid, 0, 0xf, tsr0); WRITE_TSR(sc, cid, 4, 0xf, tsr4); WRITE_TSR(sc, cid, 1, 0xf, ((hatm_cps2atmf(t->pcr) << HE_REGS_TSR1_PCR) | (hatm_cps2atmf(t->mcr) << HE_REGS_TSR1_MCR))); WRITE_TSR(sc, cid, 2, 0xf, (hatm_cps2atmf(t->icr) << HE_REGS_TSR2_ACR)); WRITE_TSR(sc, cid, 3, 0xf, ((NRM_CODE2VAL(t->nrm) - 1) << HE_REGS_TSR3_NRM) | (crm << HE_REGS_TSR3_CRM)); WRITE_TSR(sc, cid, 5, 0xf, 0); WRITE_TSR(sc, cid, 6, 0xf, 0); WRITE_TSR(sc, cid, 7, 0xf, 0); WRITE_TSR(sc, cid, 8, 0xf, 0); WRITE_TSR(sc, cid, 10, 0xf, 0); WRITE_TSR(sc, cid, 12, 0xf, 0); WRITE_TSR(sc, cid, 14, 0xf, 0); WRITE_TSR(sc, cid, 9, 0xf, HE_REGM_TSR9_INIT); WRITE_TSR(sc, cid, 11, 0xf, (hatm_cps2atmf(t->icr) << HE_REGS_TSR11_ICR) | (t->trm << HE_REGS_TSR11_TRM) | (t->nrm << HE_REGS_TSR11_NRM) | (t->adtf << HE_REGS_TSR11_ADTF)); WRITE_TSR(sc, cid, 13, 0xf, (t->rdf << HE_REGS_TSR13_RDF) | (t->rif << HE_REGS_TSR13_RIF) | (t->cdf << HE_REGS_TSR13_CDF) | (crm << HE_REGS_TSR13_CRM)); break; default: return; } vcc->vflags |= HE_VCC_TX_OPEN; } /* * Close the TX side of a VCC. Set the CLOSING flag. */ void hatm_tx_vcc_close(struct hatm_softc *sc, u_int cid) { struct hevcc *vcc = sc->vccs[cid]; struct tpd *tpd_list[1]; u_int i, pcr = 0; WRITE_TSR(sc, cid, 4, 0x8, HE_REGM_TSR4_FLUSH); switch (vcc->param.traffic) { case ATMIO_TRAFFIC_CBR: WRITE_TSR(sc, cid, 14, 0x8, HE_REGM_TSR14_CBR_DELETE); break; case ATMIO_TRAFFIC_ABR: WRITE_TSR(sc, cid, 14, 0x4, HE_REGM_TSR14_ABR_CLOSE); pcr = vcc->param.tparam.pcr; /* FALL THROUGH */ case ATMIO_TRAFFIC_UBR: WRITE_TSR(sc, cid, 1, 0xf, hatm_cps2atmf(HE_CONFIG_FLUSH_RATE) << HE_REGS_TSR1_MCR | hatm_cps2atmf(pcr) << HE_REGS_TSR1_PCR); break; } tpd_list[0] = hatm_alloc_tpd(sc, 0); tpd_list[0]->tpd.addr |= HE_REGM_TPD_EOS | HE_REGM_TPD_INTR; tpd_list[0]->cid = cid; vcc->vflags |= HE_VCC_TX_CLOSING; vcc->vflags &= ~HE_VCC_TX_OPEN; i = 0; while (hatm_queue_tpds(sc, 1, tpd_list, cid) != 0) { if (++i == 1000) panic("TPDRQ permanently full"); DELAY(1000); } } void hatm_tx_vcc_closed(struct hatm_softc *sc, u_int cid) { if (sc->vccs[cid]->param.traffic == ATMIO_TRAFFIC_CBR) { sc->cbr_bw -= sc->vccs[cid]->param.tparam.pcr; sc->rate_ctrl[sc->vccs[cid]->rc].refcnt--; } }