freebsd-dev/sys/dev/hfa/fore_output.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

416 lines
9.0 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: 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);
}