summaryrefslogtreecommitdiffstats
path: root/sys/dev/hea/eni_transmit.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/hea/eni_transmit.c')
-rw-r--r--sys/dev/hea/eni_transmit.c823
1 files changed, 823 insertions, 0 deletions
diff --git a/sys/dev/hea/eni_transmit.c b/sys/dev/hea/eni_transmit.c
new file mode 100644
index 0000000..25066df
--- /dev/null
+++ b/sys/dev/hea/eni_transmit.c
@@ -0,0 +1,823 @@
+/*
+ *
+ * ===================================
+ * 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: eni_transmit.c,v 1.20 1998/07/17 20:20:16 root Exp $
+ *
+ */
+
+/*
+ * Efficient ENI Adapter Support
+ * -----------------------------
+ *
+ * Transmit queue management and PDU output processing
+ *
+ */
+
+
+#ifndef lint
+static char *RCSid = "@(#) $Id: eni_transmit.c,v 1.20 1998/07/17 20:20:16 root Exp $";
+#endif
+
+#include <netatm/kern_include.h>
+
+#include <dev/hea/eni_stats.h>
+#include <dev/hea/eni.h>
+#include <dev/hea/eni_var.h>
+
+/*
+ * Make a variable which controls printing of PDUs
+ * as they travel through the driver.
+ */
+#ifdef DIAGNOSTIC
+int eni_pdu_print = 0;
+#endif
+
+/*
+ * Some PCI chipsets do not handle one or more of the 8WORD or
+ * 4WORD DMA transfer sizes. Default to using only 1WORD transfer
+ * sizes unless the user wishes to experiment.
+ *
+ * Make sure that these have to be changed here in this module.
+ */
+#define DMA_USE_8WORD
+#define DMA_USE_4WORD
+
+/*
+ * Create a DMA list entry
+ *
+ * DMA entries consist of a control word and a physical address.
+ * Control words are comprised of a DMA type, a count of type transfers
+ * to occur, and a variable which for TX requests is the TX channel
+ * number and for RX requests is the VCC number.
+ *
+ * Arguments:
+ * eup pointer to unit structure
+ * rx set if receiving
+ * dma_list pointer to DMA list structure
+ * list_size length of DMA list structure
+ * idx pointer to current list entry
+ * val TX channel or RX vcc
+ * addr virtual DMA address of data buffer
+ * size size in bytes of DMA request to be built
+ *
+ * Returns:
+ * dma_list updated with new entries
+ * idx points to next list entry
+ * -1 no room in DMA list structure or DMA_GET_ADDR failed
+ */
+int
+eni_set_dma ( eup, rx, dma_list, list_size, idx, val, addr, size )
+Eni_unit *eup;
+u_long *dma_list;
+int list_size;
+long *idx;
+int val;
+u_long addr;
+int size;
+{
+ int dsize; /* Size of current DMA request */
+
+ /*
+ * Round up to multiple of word and convert to number
+ * of words rather then number of bytes.
+ */
+ size = ( size + 3 ) >> 2;
+
+#ifdef DMA_USE_8WORD
+ /*
+ * Check for room in DMA list - we need two entires
+ */
+ if ( *idx + 2 >= list_size )
+ return ( -1 );
+
+ /*
+ * Here is the big win. Move as much data possible with
+ * n 8WORD DMAs.
+ */
+ /*
+ * Check if we can do one or more 8WORD DMAs
+ */
+ dsize = size & ~7;
+ if ( dsize ) {
+ dma_list[(*idx)++] = ( dsize >> 3 ) << DMA_COUNT_SHIFT |
+ val << DMA_VCC_SHIFT | DMA_8WORD;
+ dma_list[*idx] = (u_long)DMA_GET_ADDR ( addr, dsize, 0, 0 );
+ if ( dma_list[*idx] == NULL ) {
+ if ( rx )
+ eup->eu_stats.eni_st_drv.drv_rv_segdma++;
+ else
+ eup->eu_stats.eni_st_drv.drv_xm_segdma++;
+ return ( -1 ); /* DMA_GET_ADDR failed */
+ } else
+ (*idx)++; /* increment index */
+ /*
+ * Adjust addr and size
+ */
+ addr += dsize << 2;
+ size &= 7;
+ }
+#endif /* DMA_USE_8WORD */
+
+#ifdef DMA_USE_4WORD
+ /*
+ * Check for room in DMA list - we need two entries
+ */
+ if ( *idx + 2 >= list_size )
+ return ( -1 );
+
+ /*
+ * Kindof a tossup from this point on. Since we hacked as many
+ * 8WORD DMAs off as possible, we are left with 0-7 words
+ * of remaining data. We could do upto one 4WORD with 0-3
+ * words left, or upto three 2WORDS with 0-1 words left,
+ * or upto seven WORDS with nothing left. Someday we should
+ * experiment with performance and see if any particular
+ * combination is a better win then some other...
+ */
+ /*
+ * Check if we can do one or more 4WORD DMAs
+ */
+ dsize = size & ~3;
+ if ( dsize ) {
+ dma_list[(*idx)++] = ( dsize >> 2 ) << DMA_COUNT_SHIFT |
+ val << DMA_VCC_SHIFT | DMA_4WORD;
+ dma_list[*idx] = (u_long)DMA_GET_ADDR ( addr, dsize, 0, 0 );
+ if ( dma_list[*idx] == NULL ) {
+ if ( rx )
+ eup->eu_stats.eni_st_drv.drv_rv_segdma++;
+ else
+ eup->eu_stats.eni_st_drv.drv_xm_segdma++;
+ return ( -1 ); /* DMA_GET_ADDR failed */
+ } else
+ (*idx)++; /* increment index */
+ /*
+ * Adjust addr and size
+ */
+ addr += dsize << 2;
+ size &= 3;
+ }
+#endif /* DMA_USE_4WORD */
+
+ /*
+ * Check for room in DMA list - we need two entries
+ */
+ if ( *idx + 2 >= list_size )
+ return ( -1 );
+
+ /*
+ * Hard to know if one 2WORD and 0/1 WORD DMA would be better
+ * then 2/3 WORD DMAs. For now, skip 2WORD DMAs in favor of
+ * WORD DMAs.
+ */
+
+ /*
+ * Finish remaining size a 1WORD DMAs
+ */
+ if ( size ) {
+ dma_list[(*idx)++] = ( size ) << DMA_COUNT_SHIFT |
+ val << DMA_VCC_SHIFT | DMA_WORD;
+ dma_list[*idx] = (u_long)DMA_GET_ADDR ( addr, size, 0, 0 );
+ if ( dma_list[*idx] == NULL ) {
+ if ( rx )
+ eup->eu_stats.eni_st_drv.drv_rv_segdma++;
+ else
+ eup->eu_stats.eni_st_drv.drv_xm_segdma++;
+ return ( -1 ); /* DMA_GET_ADDR failed */
+ } else
+ (*idx)++; /* increment index */
+ }
+
+ /*
+ * Inserted descriptor okay
+ */
+ return 0;
+}
+
+/*
+ * Drain Transmit queue
+ *
+ * As PDUs are given to the adapter to be transmitted, we
+ * place them into a private ifqueue so that we can free
+ * any resources AFTER we know they've been successfully DMAed.
+ * As part of the output processing, we record the PDUs start
+ * and stop entries in the DMA list, and prevent wrapping. When
+ * we pull the top element off, we simply check that the current
+ * DMA location is outside this PDU and if so, it's okay to free
+ * things.
+ *
+ * PDUs are always in ascending order in the queue.
+ *
+ * Arguments:
+ * eup pointer to device unit structure
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+eni_xmit_drain ( eup )
+ Eni_unit *eup;
+{
+ KBuffer *m;
+ Eni_vcc *evp;
+ struct vccb *vcp;
+ u_long pdulen;
+ u_long start, stop;
+ u_long dmap;
+ int s = splimp();
+
+ /*
+ * Pull the top element (PDU) off
+ */
+ IF_DEQUEUE ( &eup->eu_txqueue, m );
+ /*
+ * As long as there are valid elements
+ */
+ while ( m ) {
+ u_long *up;
+
+ /*
+ * Find start of buffer
+ */
+ KB_DATASTART ( m, up, u_long * );
+
+ /*
+ * First word is the VCC for this PDU
+ */
+ /*
+ * NOTE: There is a potential problem here in that
+ * if the VCC is closed after this buffer was transmitted
+ * but before we get here, that while evp is non-null,
+ * it will not reference a valid vccb. We need to either
+ * delay closing the VCC until all references are removed
+ * from the drain stacks, actually go through the drain
+ * stacks and remove any references, or find someway of
+ * indicating that this vccb is nolonger usable.
+ */
+ evp = (Eni_vcc *)*up++;
+ /*
+ * Second word is the start and stop DMA pointers
+ */
+ start = *up >> 16;
+ stop = *up++ & 0xffff;
+ /*
+ * Find out where the TX engine is at
+ */
+ dmap = eup->eu_midway[MIDWAY_TX_RD];
+ /*
+ * Check to see if TX engine has processed this
+ * PDU yet. Remember that everything is circular
+ * and that stop might be less than start numerically.
+ */
+ if ( start > stop ) {
+ if ( !(dmap >= stop && dmap < start) ) {
+ /*
+ * Haven't finished this PDU yet - replace
+ * it as the head of list.
+ */
+ IF_PREPEND ( &eup->eu_txqueue, m );
+ /*
+ * If this one isn't done, none of the others
+ * are either.
+ */
+ (void) splx(s);
+ return;
+ }
+ } else {
+ if ( dmap < stop && dmap >= start ) {
+ /*
+ * Haven't finished this PDU yet - replace
+ * it as the head of list.
+ */
+ IF_PREPEND ( &eup->eu_txqueue, m );
+ /*
+ * If this one isn't done, none of the others
+ * are either.
+ */
+ (void) splx(s);
+ return;
+ }
+ }
+
+ /*
+ * Count the PDU stats for this interface
+ */
+ eup->eu_pif.pif_opdus++;
+ /*
+ * Third word is PDU length from eni_output().
+ */
+ pdulen = *up++;
+ eup->eu_txfirst = (eup->eu_txfirst + *up) &
+ (eup->eu_txsize - 1);
+ eup->eu_pif.pif_obytes += pdulen;
+
+ /*
+ * Now lookup the VCC entry and counts the stats for
+ * this VC.
+ */
+ if ( evp ) {
+ vcp = evp->ev_connvc->cvc_vcc;
+ if ( vcp ) {
+ vcp->vc_opdus++;
+ vcp->vc_obytes += pdulen;
+ /*
+ * If we also have a network interface, count the PDU
+ * there also.
+ */
+ if ( vcp->vc_nif ) {
+ vcp->vc_nif->nif_obytes += pdulen;
+ vcp->vc_nif->nif_if.if_opackets++;
+#if (defined(BSD) && (BSD >= 199103))
+ vcp->vc_nif->nif_if.if_obytes += pdulen;
+#endif
+ }
+ }
+ }
+ /*
+ * Free the buffer chain
+ */
+ KB_FREEALL ( m );
+
+ /*
+ * Advance DMA write okay pointer
+ */
+ eup->eu_txdmawr = stop;
+
+ /*
+ * Look for next completed transmit PDU
+ */
+ IF_DEQUEUE ( &eup->eu_txqueue, m );
+ }
+ /*
+ * We've drained the queue...
+ */
+ (void) splx(s);
+ return;
+}
+
+/*
+ * 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 ENI-specific details.
+ * Then we just build a segmentation structure for the PDU and place the
+ * address into the DMA_Transmit_queue.
+ *
+ * Arguments:
+ * cup pointer to device common unit
+ * cvp pointer to common VCC entry
+ * m pointer to output PDU buffer chain head
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+eni_output ( cup, cvp, m )
+ Cmn_unit *cup;
+ Cmn_vcc *cvp;
+ KBuffer *m;
+{
+ Eni_unit *eup = (Eni_unit *)cup;
+ Eni_vcc *evp = (Eni_vcc *)cvp;
+ int s, s2;
+ int pdulen = 0;
+ u_long size;
+ u_long buf_avail;
+ u_long dma_rd, dma_wr;
+ u_long dma[TEMP_DMA_SIZE];
+ int aal5, i;
+ long j;
+ u_long dma_avail;
+ u_long dma_start;
+ Eni_mem tx_send;
+ u_long *up;
+ KBuffer *m0 = m, *m1, *mprev = NULL;
+ caddr_t cp, bfr;
+ u_int len, align;
+ int compressed = 0;
+
+#ifdef DIAGNOSTIC
+ if ( eni_pdu_print )
+ atm_dev_pdu_print ( cup, cvp, m, "eni output" );
+#endif
+
+ /*
+ * Re-entry point for after buffer compression (if needed)
+ */
+retry:
+
+ /*
+ * We can avoid traversing the buffer list twice by building
+ * the middle (minus header and trailer) dma list at the
+ * same time we massage address and size alignments. Since
+ * this list remains local until we determine we've enough
+ * room, we're not going to trash anything by not checking
+ * sizes, etc. yet. Skip first entry to be used later to skip
+ * descriptor word.
+ */
+ j = 2;
+ /*
+ * Do data positioning for address and length alignment
+ */
+ while ( m ) {
+ u_long buf_addr; /* For passing addr to eni_set_dma() */
+
+ /*
+ * 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;
+ }
+ /*
+ * Get start of data onto full-word alignment
+ */
+ KB_DATASTART ( m, cp, caddr_t );
+ if ( align = ((u_int)cp) & (sizeof(u_long)-1)) {
+ /*
+ * Gotta slide the data up
+ */
+ eup->eu_stats.eni_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 & (sizeof(u_long)-1))) &&
+ (m1 = KB_NEXT ( m ) ) ) {
+
+ /*
+ * Have to move some data from following buffer(s)
+ * to word-fill this buffer
+ */
+ u_int ncopy = MIN ( sizeof(u_long) - align,
+ KB_LEN ( m1 ) );
+
+ if ( ncopy ) {
+ /*
+ * Move data to current buffer
+ */
+ caddr_t dest;
+
+ eup->eu_stats.eni_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 );
+ }
+ }
+
+ /*
+ * Address and size are now aligned. Build dma list
+ * using TX channel 0. Also, round length up to a word
+ * size which should only effect the last buffer in the
+ * chain. This works because the PDU length is maintained
+ * seperately and we're not really adjusting the buffer's
+ * idea of its length.
+ */
+ KB_DATASTART ( m, buf_addr, u_long );
+ if ( eni_set_dma ( eup, 0, dma, TEMP_DMA_SIZE, &j, 0,
+ buf_addr, KB_LEN ( m ) ) < 0 ) {
+ /*
+ * Failed to build DMA list. First, we'll try to
+ * compress the buffer chain into a smaller number
+ * of buffers. After compressing, we'll try to send
+ * the new buffer chain. If we still fail, then
+ * we'll drop the pdu.
+ */
+ if ( compressed ) {
+#ifdef DO_LOG
+ log ( LOG_ERR,
+ "eni_output: eni_set_dma failed\n" );
+#endif
+ eup->eu_pif.pif_oerrors++;
+ KB_FREEALL ( m0 );
+ return;
+ }
+
+ eup->eu_stats.eni_st_drv.drv_xm_maxpdu++;
+
+ m = atm_dev_compress ( m0 );
+ if ( m == NULL ) {
+#ifdef DO_LOG
+ log ( LOG_ERR,
+ "eni_output: atm_dev_compress() failed\n" );
+#endif
+ eup->eu_pif.pif_oerrors++;
+ return;
+ }
+
+ /*
+ * Reset to new head of buffer chain
+ */
+ m0 = m;
+
+ /*
+ * Indicate we've been through here
+ */
+ compressed = 1;
+
+ /*
+ * Retry to build the DMA descriptors for the newly
+ * compressed buffer chain
+ */
+ goto retry;
+
+ }
+
+ /*
+ * Now count the length
+ */
+ pdulen += KB_LEN ( m );
+
+ /*
+ * Bump counters and get ready for next buffer
+ */
+ mprev = m;
+ m = KB_NEXT ( m );
+ }
+
+ /*
+ * Get a buffer to use in a private queue so that we can
+ * reclaim resources after the DMA has finished.
+ */
+ KB_ALLOC ( m, ENI_SMALL_BSIZE, KB_F_NOWAIT, KB_T_DATA );
+ if ( m ) {
+ /*
+ * Link the PDU onto our new head
+ */
+ KB_NEXT ( m ) = m0;
+ } else {
+ /*
+ * Drop this PDU and let the sender try again.
+ */
+ eup->eu_stats.eni_st_drv.drv_xm_norsc++;
+#ifdef DO_LOG
+ log(LOG_ERR, "eni_output: Unable to allocate drain buffer.\n");
+#endif
+ eup->eu_pif.pif_oerrors++;
+ KB_FREEALL ( m0 );
+ return;
+ }
+
+ s = splnet();
+
+ /*
+ * Calculate size of buffer necessary to store PDU. If this
+ * is an AAL5 PDU, we'll need to know where to stuff the length
+ * value in the trailer.
+ */
+ /*
+ * AAL5 PDUs need an extra two words for control/length and
+ * CRC. Check for AAL5 and add requirements here.
+ */
+ if (aal5 = (evp->ev_connvc->cvc_attr.aal.type == ATM_AAL5))
+ size = pdulen + 2 * sizeof(long);
+ else
+ size = pdulen;
+ /*
+ * Pad to next complete cell boundary
+ */
+ size += (BYTES_PER_CELL - 1);
+ size -= size % BYTES_PER_CELL;
+ /*
+ * Convert size to words and add 2 words overhead for every
+ * PDU (descriptor and cell header).
+ */
+ size = (size >> 2) + 2;
+
+ /*
+ * First, check to see if there's enough buffer space to
+ * store the PDU. We do this by checking to see if the size
+ * required crosses the eu_txfirst pointer. However, we don't
+ * want to exactly fill the buffer, because we won't be able to
+ * distinguish between a full and empty buffer.
+ */
+ if ( eup->eu_txpos == eup->eu_txfirst )
+ buf_avail = eup->eu_txsize;
+ else
+ if ( eup->eu_txpos > eup->eu_txfirst )
+ buf_avail = eup->eu_txsize - ( eup->eu_txpos - eup->eu_txfirst );
+ else
+ buf_avail = eup->eu_txfirst - eup->eu_txpos;
+
+ if ( size >= buf_avail )
+ {
+ /*
+ * No buffer space in the adapter to store this PDU.
+ * Drop PDU and return.
+ */
+ eup->eu_stats.eni_st_drv.drv_xm_nobuf++;
+#ifdef DO_LOG
+ log ( LOG_ERR,
+ "eni_output: not enough room in buffer\n" );
+#endif
+ eup->eu_pif.pif_oerrors++;
+ KB_FREEALL ( m );
+ (void) splx(s);
+ return;
+ }
+
+ /*
+ * Find out where current DMA pointers are at
+ */
+ dma_start = dma_wr = eup->eu_midway[MIDWAY_TX_WR];
+ dma_rd = eup->eu_midway[MIDWAY_TX_RD];
+
+ /*
+ * Figure out how much DMA room we have available
+ */
+ if ( dma_rd == dma_wr ) { /* Queue is empty */
+ dma_avail = DMA_LIST_SIZE;
+ } else {
+ dma_avail = ( dma_rd + DMA_LIST_SIZE - dma_wr )
+ & ( DMA_LIST_SIZE - 1 );
+ }
+ /*
+ * Check to see if we can describe this PDU or if we're:
+ * out of room, will wrap past recovered resources.
+ */
+ if ( dma_avail < (j / 2 + 4) ||
+ ( dma_wr + (j / 2 + 4) > eup->eu_txdmawr + DMA_LIST_SIZE ) ) {
+ /*
+ * No space to insert DMA list into queue. Drop this PDU.
+ */
+ eup->eu_stats.eni_st_drv.drv_xm_nodma++;
+#ifdef DO_LOG
+ log ( LOG_ERR,
+ "eni_output: not enough room in DMA queue\n" );
+#endif
+ eup->eu_pif.pif_oerrors++;
+ KB_FREEALL( m );
+ (void) splx(s);
+ return;
+ }
+
+ /*
+ * Create DMA descriptor for header. There is a descriptor word
+ * and also a cell header word which we'll set manually.
+ */
+ dma[0] = (((int)(eup->eu_txpos + 2) & (eup->eu_txsize-1)) <<
+ DMA_COUNT_SHIFT) | DMA_JK;
+ dma[1] = 0;
+
+ /*
+ * JK for AAL5 trailer. Set END bit as well.
+ */
+ if ( aal5 ) {
+ dma[j++] = (((int)(eup->eu_txpos+size) & (eup->eu_txsize-1)) <<
+ DMA_COUNT_SHIFT) | DMA_END_BIT | DMA_JK;
+ dma[j++] = 0;
+ } else {
+ dma[j-2] |= DMA_END_BIT; /* Backup and set END bit */
+ }
+
+ /*
+ * Find out where in adapter memory this TX buffer starts.
+ */
+ tx_send = (Eni_mem)
+ ((((int)eup->eu_midway[MIDWAY_TXPLACE] & 0x7ff) << ENI_LOC_PREDIV) +
+ (int)eup->eu_ram);
+
+ /*
+ * Set descriptor word
+ */
+ tx_send[eup->eu_txpos] =
+ (MIDWAY_UNQ_ID << 28) | (aal5 ? 1 << 27 : 0)
+ | (size / WORDS_PER_CELL);
+ /*
+ * Set cell header
+ */
+ tx_send[(eup->eu_txpos+1)&(eup->eu_txsize-1)] =
+ evp->ev_connvc->cvc_vcc->vc_vci << 4;
+
+ /*
+ * We've got all our resources, count the stats
+ */
+ if ( aal5 ) {
+ /*
+ * If this is an AAL5 PDU, we need to set the length
+ */
+ tx_send[(eup->eu_txpos+size-2) &
+ (eup->eu_txsize-1)] = pdulen;
+ /*
+ * Increment AAL5 stats
+ */
+ eup->eu_stats.eni_st_aal5.aal5_pdu_xmit++;
+ eup->eu_stats.eni_st_aal5.aal5_xmit += (size - 2) / WORDS_PER_CELL;
+ } else {
+ /*
+ * Increment AAL0 stats
+ */
+ eup->eu_stats.eni_st_aal0.aal0_xmit += (size - 2) / WORDS_PER_CELL;
+ }
+ /*
+ * Increment ATM stats
+ */
+ eup->eu_stats.eni_st_atm.atm_xmit += (size - 2) / WORDS_PER_CELL;
+
+ /*
+ * Store the DMA list
+ */
+ j = j >> 1;
+ for ( i = 0; i < j; i++ ) {
+ eup->eu_txdma[dma_wr*2] = dma[i*2];
+ eup->eu_txdma[dma_wr*2+1] = dma[i*2+1];
+ dma_wr = (dma_wr+1) & (DMA_LIST_SIZE-1);
+ }
+
+ /*
+ * Build drain buffer
+ *
+ * We toss four words in to help keep track of this
+ * PDU. The first is a pointer to the VC control block
+ * so we can find which VCI this went out on, the second
+ * is the start and stop pointers for the DMA list which
+ * describes this PDU, the third is the PDU length
+ * since we'll want to know that for stats gathering,
+ * and the fourth is the number of DMA words.
+ */
+ KB_DATASTART ( m, up, u_long * );
+ *up++ = (u_long)cvp;
+ *up++ = dma_start << 16 | dma_wr;
+ *up++ = pdulen;
+ *up = size;
+
+ /*
+ * Set length of our buffer
+ */
+ KB_LEN ( m ) = 4 * sizeof ( long );
+
+ /*
+ * Place buffers onto transmit queue for draining
+ */
+ s2 = splimp();
+ IF_ENQUEUE ( &eup->eu_txqueue, m );
+ (void) splx(s2);
+
+ /*
+ * Update next word to be stored
+ */
+ eup->eu_txpos = ((eup->eu_txpos + size) & (eup->eu_txsize - 1));
+
+ /*
+ * Update MIDWAY_TX_WR pointer
+ */
+ eup->eu_midway[MIDWAY_TX_WR] = dma_wr;
+
+ (void) splx ( s );
+
+ return;
+}
+
OpenPOWER on IntegriCloud