/*-
 *
 * ===================================
 * 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.
 *
 *	@(#) $FreeBSD: src/sys/dev/hfa/fore_output.c,v 1.20 2005/06/10 16:49:09 brooks Exp $
 *
 */

/*
 * FORE Systems 200-Series Adapter Support
 * ---------------------------------------
 *
 * PDU output processing
 *
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <net/if.h>
#include <netatm/port.h>
#include <netatm/queue.h>
#include <netatm/atm.h>
#include <netatm/atm_sys.h>
#include <netatm/atm_sap.h>
#include <netatm/atm_cm.h>
#include <netatm/atm_if.h>
#include <netatm/atm_vc.h>
#include <netatm/atm_stack.h>
#include <netatm/atm_pcb.h>
#include <netatm/atm_var.h>
#include <dev/pci/pcivar.h>
#include <dev/hfa/fore.h>
#include <dev/hfa/fore_aali.h>
#include <dev/hfa/fore_slave.h>
#include <dev/hfa/fore_stats.h>
#include <dev/hfa/fore_var.h>
#include <dev/hfa/fore_include.h>

#ifndef lint
__RCSID("@(#) $FreeBSD: src/sys/dev/hfa/fore_output.c,v 1.20 2005/06/10 16:49:09 brooks Exp $");
#endif


/*
 * Local functions
 */
static KBuffer *	fore_xmit_segment(Fore_unit *, KBuffer *,
				H_xmit_queue *, int *, int *);
static void		fore_seg_dma_free(H_xmit_queue *, KBuffer *, 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;
	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)
				ANIF2IFP(vcp->vc_nif)->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)
				ANIF2IFP(vcp->vc_nif)->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)
			ANIF2IFP(vcp->vc_nif)->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 = fvp->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;
	int		*segp;
	int		*lenp;
{
	Xmit_descr	*xdp = hxp->hxq_descr;
	Xmit_seg_descr	*xsp;
	H_dma		*sdmap;
	KBuffer		*m0, *m1, *mprev;
	caddr_t		cp, bfr;
	vm_paddr_t	dma;
	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) {
			/*
			 * First, free already allocated DMA addresses
			 */
			fore_seg_dma_free(hxp, m0, nsegs);

			/*
			 * 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 = ((uintptr_t)cp) & (XMIT_SEG_ALIGN - 1)) != 0) {
			/*
			 * Gotta slide the data up
			 */
			fup->fu_stats->st_drv.drv_xm_segnoal++;
			bfr = cp - align;
			bcopy(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
			 */
			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 = vtophys(bfr);
		if (dma == 0) {
			fup->fu_stats->st_drv.drv_xm_segdma++;
			fore_seg_dma_free(hxp, m0, nsegs);
			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++;
		fore_seg_dma_free(hxp, m0, nsegs);
		KB_FREEALL(m0);
		return (NULL);
	}

	/*
	 * Return the good news to the caller
	 */
	*segp = nsegs;
	*lenp = pdulen;

	return (m0);
}


/*
 * Free Transmit Segment Queue DMA addresses
 * 
 * Arguments:
 *	hxp	pointer to host transmit queue entry
 *	m0	pointer to output PDU buffer chain head
 *	nsegs	number of processed transmit segments
 *
 * Returns:
 *	none
 *
 */
static void
fore_seg_dma_free(hxp, m0, nsegs)
	H_xmit_queue	*hxp;
	KBuffer		*m0;
	int		nsegs;
{
	KBuffer		*m = m0;
	H_dma		*sdmap = hxp->hxq_dma;
	caddr_t         cp;
	int		i;

	for (i = 0; i < nsegs; i++) {
		KB_DATASTART(m, cp, caddr_t);
		m = KB_NEXT(m);
		sdmap++;
	}
}

