summaryrefslogtreecommitdiffstats
path: root/sys/dev/hfa/fore_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/hfa/fore_output.c')
-rw-r--r--sys/dev/hfa/fore_output.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/sys/dev/hfa/fore_output.c b/sys/dev/hfa/fore_output.c
new file mode 100644
index 0000000..59c82c9
--- /dev/null
+++ b/sys/dev/hfa/fore_output.c
@@ -0,0 +1,415 @@
+/*
+ *
+ * ===================================
+ * HARP | Host ATM Research Platform
+ * ===================================
+ *
+ *
+ * This Host ATM Research Platform ("HARP") file (the "Software") is
+ * made available by Network Computing Services, Inc. ("NetworkCS")
+ * "AS IS". NetworkCS does not provide maintenance, improvements or
+ * support of any kind.
+ *
+ * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
+ * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
+ * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
+ * In no event shall NetworkCS be responsible for any damages, including
+ * but not limited to consequential damages, arising from or relating to
+ * any use of the Software or related support.
+ *
+ * Copyright 1994-1998 Network Computing Services, Inc.
+ *
+ * Copies of this Software may be made, however, the above copyright
+ * notice must be reproduced on all copies.
+ *
+ * @(#) $Id: fore_output.c,v 1.7 1998/02/19 20:10:34 mks Exp $
+ *
+ */
+
+/*
+ * FORE Systems 200-Series Adapter Support
+ * ---------------------------------------
+ *
+ * PDU output processing
+ *
+ */
+
+#ifndef lint
+static char *RCSid = "@(#) $Id: fore_output.c,v 1.7 1998/02/19 20:10:34 mks Exp $";
+#endif
+
+#include <dev/hfa/fore_include.h>
+
+
+/*
+ * Local functions
+ */
+static KBuffer * fore_xmit_segment __P((Fore_unit *, KBuffer *,
+ H_xmit_queue *, u_int *, u_int *));
+
+
+/*
+ * Output a PDU
+ *
+ * This function is called via the common driver code after receiving a
+ * stack *_DATA* command. The common code has already validated most of
+ * the request so we just need to check a few more Fore-specific details.
+ * Then we just build a transmit descriptor request for the PDU and issue
+ * the command to the CP.
+ *
+ * Arguments:
+ * cup pointer to device common unit
+ * cvp pointer to common VCC entry
+ * m pointer to output PDU buffer chain head
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+fore_output(cup, cvp, m)
+ Cmn_unit *cup;
+ Cmn_vcc *cvp;
+ KBuffer *m;
+{
+ Fore_unit *fup = (Fore_unit *)cup;
+ Fore_vcc *fvp = (Fore_vcc *)cvp;
+ struct vccb *vcp;
+ H_xmit_queue *hxp;
+ Xmit_queue *cqp;
+ Xmit_descr *xdp;
+ u_int retry, nsegs, pdulen;
+ int s;
+
+#ifdef DIAGNOSTIC
+ if (atm_dev_print)
+ atm_dev_pdu_print(cup, cvp, m, "fore_output");
+#endif
+
+ vcp = fvp->fv_connvc->cvc_vcc;
+
+ /*
+ * If we're still waiting for activation to finish, delay for
+ * a little while before we toss the PDU
+ */
+ if (fvp->fv_state == CVS_INITED) {
+ retry = 3;
+ while (retry-- && (fvp->fv_state == CVS_INITED))
+ DELAY(1000);
+ if (fvp->fv_state != CVS_ACTIVE) {
+ /*
+ * Activation still hasn't finished, oh well....
+ */
+ fup->fu_stats->st_drv.drv_xm_notact++;
+ vcp->vc_oerrors++;
+ if (vcp->vc_nif)
+ vcp->vc_nif->nif_if.if_oerrors++;
+ KB_FREEALL(m);
+ return;
+ }
+ }
+
+ /*
+ * Queue PDU at end of transmit queue
+ *
+ * If queue is full we'll delay a bit before tossing the PDU
+ */
+ s = splnet();
+ hxp = fup->fu_xmit_tail;
+ if (!((*hxp->hxq_status) & QSTAT_FREE)) {
+
+ fup->fu_stats->st_drv.drv_xm_full++;
+ retry = 3;
+ do {
+ DELAY(1000);
+
+ DEVICE_LOCK((Cmn_unit *)fup);
+ fore_xmit_drain(fup);
+ DEVICE_UNLOCK((Cmn_unit *)fup);
+
+ } while (--retry && (!((*hxp->hxq_status) & QSTAT_FREE)));
+
+ if (!((*hxp->hxq_status) & QSTAT_FREE)) {
+ /*
+ * Queue is still full, bye-bye PDU
+ */
+ fup->fu_pif.pif_oerrors++;
+ vcp->vc_oerrors++;
+ if (vcp->vc_nif)
+ vcp->vc_nif->nif_if.if_oerrors++;
+ KB_FREEALL(m);
+ (void) splx(s);
+ return;
+ }
+ }
+
+ /*
+ * We've got a free transmit queue entry
+ */
+
+ /*
+ * Now build the transmit segment descriptors for this PDU
+ */
+ m = fore_xmit_segment(fup, m, hxp, &nsegs, &pdulen);
+ if (m == NULL) {
+ /*
+ * The build failed, buffer chain has been freed
+ */
+ vcp->vc_oerrors++;
+ if (vcp->vc_nif)
+ vcp->vc_nif->nif_if.if_oerrors++;
+ (void) splx(s);
+ return;
+ }
+
+ /*
+ * Set up the descriptor header
+ */
+ xdp = hxp->hxq_descr;
+ xdp->xd_cell_hdr = ATM_HDR_SET(vcp->vc_vpi, vcp->vc_vci, 0, 0);
+ xdp->xd_spec = XDS_SET_SPEC(0, fvp->fv_aal, nsegs, pdulen);
+ xdp->xd_rate = FORE_DEF_RATE;
+
+ /*
+ * Everything is ready to go, so officially claim the host queue
+ * entry and setup the CP-resident queue entry. The CP will grab
+ * the PDU when the descriptor pointer is set.
+ */
+ fup->fu_xmit_tail = hxp->hxq_next;
+ hxp->hxq_buf = m;
+ hxp->hxq_vcc = fvp;
+ (*hxp->hxq_status) = QSTAT_PENDING;
+ cqp = hxp->hxq_cpelem;
+ cqp->cq_descr = (CP_dma)
+ CP_WRITE((u_long)hxp->hxq_descr_dma | XMIT_SEGS_TO_BLKS(nsegs));
+
+ (void) splx(s);
+
+ /*
+ * See if there are any completed queue entries
+ */
+ DEVICE_LOCK((Cmn_unit *)fup);
+ fore_xmit_drain(fup);
+ DEVICE_UNLOCK((Cmn_unit *)fup);
+
+ return;
+}
+
+
+/*
+ * Build Transmit Segment Descriptors
+ *
+ * This function will take a supplied buffer chain of data to be transmitted
+ * and build the transmit segment descriptors for the data. This will include
+ * the dreaded operation of ensuring that the data for each transmit segment
+ * is full-word aligned and (except for the last segment) is an integral number
+ * of words in length. If the data isn't already aligned and sized as
+ * required, then the data must be shifted (copied) into place - a sure
+ * performance killer. Note that we rely on the fact that all buffer data
+ * areas are allocated with (at least) full-word alignments/lengths.
+ *
+ * If any errors are encountered, the buffer chain will be freed.
+ *
+ * Arguments:
+ * fup pointer to device unit
+ * m pointer to output PDU buffer chain head
+ * hxp pointer to host transmit queue entry
+ * segp pointer to return the number of transmit segments
+ * lenp pointer to return the pdu length
+ *
+ * Returns:
+ * m build successful, pointer to (possibly new) head of
+ * output PDU buffer chain
+ * NULL build failed, buffer chain freed
+ *
+ */
+static KBuffer *
+fore_xmit_segment(fup, m, hxp, segp, lenp)
+ Fore_unit *fup;
+ KBuffer *m;
+ H_xmit_queue *hxp;
+ u_int *segp;
+ u_int *lenp;
+{
+ Xmit_descr *xdp = hxp->hxq_descr;
+ Xmit_seg_descr *xsp;
+ H_dma *sdmap;
+ KBuffer *m0, *m1, *mprev;
+ caddr_t cp, bfr;
+ void *dma;
+ u_int pdulen, nsegs, len, align;
+ int compressed = 0;
+
+ m0 = m;
+
+retry:
+ xsp = xdp->xd_seg;
+ sdmap = hxp->hxq_dma;
+ mprev = NULL;
+ pdulen = 0;
+ nsegs = 0;
+
+ /*
+ * Loop thru each buffer in the chain, performing the necessary
+ * data positioning and then building a segment descriptor for
+ * that data.
+ */
+ while (m) {
+ /*
+ * Get rid of any zero-length buffers
+ */
+ if (KB_LEN(m) == 0) {
+ if (mprev) {
+ KB_UNLINK(m, mprev, m1);
+ } else {
+ KB_UNLINKHEAD(m, m1);
+ m0 = m1;
+ }
+ m = m1;
+ continue;
+ }
+
+ /*
+ * Make sure we don't try to use too many segments
+ */
+ if (nsegs >= XMIT_MAX_SEGS) {
+ /*
+ * Try to compress buffer chain (but only once)
+ */
+ if (compressed) {
+ KB_FREEALL(m0);
+ return (NULL);
+ }
+
+ fup->fu_stats->st_drv.drv_xm_maxpdu++;
+
+ m = atm_dev_compress(m0);
+ if (m == NULL) {
+ return (NULL);
+ }
+
+ /*
+ * Build segment descriptors for compressed chain
+ */
+ m0 = m;
+ compressed = 1;
+ goto retry;
+ }
+
+ /*
+ * Get start of data onto full-word alignment
+ */
+ KB_DATASTART(m, cp, caddr_t);
+ if (align = ((u_int)cp) & (XMIT_SEG_ALIGN - 1)) {
+ /*
+ * Gotta slide the data up
+ */
+ fup->fu_stats->st_drv.drv_xm_segnoal++;
+ bfr = cp - align;
+ KM_COPY(cp, bfr, KB_LEN(m));
+ KB_HEADMOVE(m, -align);
+ } else {
+ /*
+ * Data already aligned
+ */
+ bfr = cp;
+ }
+
+ /*
+ * Now work on getting the data length correct
+ */
+ len = KB_LEN(m);
+ while ((align = (len & (XMIT_SEG_ALIGN - 1))) &&
+ (m1 = KB_NEXT(m))) {
+
+ /*
+ * Have to move some data from following buffer(s)
+ * to word-fill this buffer
+ */
+ u_int ncopy = MIN(XMIT_SEG_ALIGN - align, KB_LEN(m1));
+
+ if (ncopy) {
+ /*
+ * Move data to current buffer
+ */
+ caddr_t dest;
+
+ fup->fu_stats->st_drv.drv_xm_seglen++;
+ KB_DATASTART(m1, cp, caddr_t);
+ dest = bfr + len;
+ KB_HEADADJ(m1, -ncopy);
+ KB_TAILADJ(m, ncopy);
+ len += ncopy;
+ while (ncopy--) {
+ *dest++ = *cp++;
+ }
+ }
+
+ /*
+ * If we've drained the buffer, free it
+ */
+ if (KB_LEN(m1) == 0) {
+ KBuffer *m2;
+
+ KB_UNLINK(m1, m, m2);
+ }
+ }
+
+ /*
+ * Finally, build the segment descriptor
+ */
+
+ /*
+ * Round last segment to fullword length (if needed)
+ */
+ if (len & (XMIT_SEG_ALIGN - 1))
+ xsp->xsd_len = KB_LEN(m) =
+ (len + XMIT_SEG_ALIGN) & ~(XMIT_SEG_ALIGN - 1);
+ else
+ xsp->xsd_len = KB_LEN(m) = len;
+
+ /*
+ * Get a DMA address for the data
+ */
+ dma = DMA_GET_ADDR(bfr, xsp->xsd_len, XMIT_SEG_ALIGN, 0);
+ if (dma == NULL) {
+
+ fup->fu_stats->st_drv.drv_xm_segdma++;
+ KB_FREEALL(m0);
+ return (NULL);
+ }
+
+ /*
+ * Now we're really ready to call it a segment
+ */
+ *sdmap++ = xsp->xsd_buffer = (H_dma) dma;
+
+ /*
+ * Bump counters and get ready for next buffer
+ */
+ pdulen += len;
+ nsegs++;
+ xsp++;
+ mprev = m;
+ m = KB_NEXT(m);
+ }
+
+ /*
+ * Validate PDU length
+ */
+ if (pdulen > XMIT_MAX_PDULEN) {
+ fup->fu_stats->st_drv.drv_xm_maxpdu++;
+ KB_FREEALL(m0);
+ return (NULL);
+ }
+
+ /*
+ * Return the good news to the caller
+ */
+ *segp = nsegs;
+ *lenp = pdulen;
+
+ return (m0);
+}
+
OpenPOWER on IntegriCloud