freebsd-dev/sys/dev/hea/eni_transmit.c
Poul-Henning Kamp 1820df7a2d Add new files for HARP3
Host ATM Research Platform (HARP), Network Computing Services, Inc.
This software was developed with the support of the Defense Advanced
Research Projects Agency (DARPA).
1998-09-15 08:23:17 +00:00

824 lines
19 KiB
C

/*
*
* ===================================
* 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;
}