/*
 *
 * ===================================
 * 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_transmit.c,v 1.4 1999/08/28 00:41:53 peter Exp $
 *
 */

/*
 * FORE Systems 200-Series Adapter Support
 * ---------------------------------------
 *
 * Transmit queue management
 *
 */

#include <dev/hfa/fore_include.h>

#ifndef lint
__RCSID("@(#) $FreeBSD: src/sys/dev/hfa/fore_transmit.c,v 1.4 1999/08/28 00:41:53 peter Exp $");
#endif


/*
 * Allocate Transmit Queue Data Structures
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	0		allocations successful
 *	else		allocation failed
 */
int
fore_xmit_allocate(fup)
	Fore_unit	*fup;
{
	void		*memp;
	H_xmit_queue	*hxp;
	int		i;

	/*
	 * Allocate non-cacheable memory for transmit status words
	 */
	memp = atm_dev_alloc(sizeof(Q_status) * XMIT_QUELEN,
			QSTAT_ALIGN, ATM_DEV_NONCACHE);
	if (memp == NULL) {
		return (1);
	}
	fup->fu_xmit_stat = (Q_status *) memp;

	memp = DMA_GET_ADDR(fup->fu_xmit_stat, sizeof(Q_status) * XMIT_QUELEN,
			QSTAT_ALIGN, ATM_DEV_NONCACHE);
	if (memp == NULL) {
		return (1);
	}
	fup->fu_xmit_statd = (Q_status *) memp;

	/*
	 * Allocate memory for transmit descriptors
	 *
	 * We will allocate the transmit descriptors individually rather than 
	 * as a single memory block, which will often be larger than a memory
	 * page.  On some systems (eg. FreeBSD) the physical addresses of 
	 * adjacent virtual memory pages are not contiguous.
	 */
	hxp = fup->fu_xmit_q;
	for (i = 0; i < XMIT_QUELEN; i++, hxp++) {

		/*
		 * Allocate a transmit descriptor for this queue entry
		 */
		hxp->hxq_descr = atm_dev_alloc(sizeof(Xmit_descr),
			XMIT_DESCR_ALIGN, 0);
		if (hxp->hxq_descr == NULL) {
			return (1);
		}

		hxp->hxq_descr_dma = DMA_GET_ADDR(hxp->hxq_descr,
			sizeof(Xmit_descr), XMIT_DESCR_ALIGN, 0);
		if (hxp->hxq_descr_dma == NULL) {
			return (1);
		}
	}

	return (0);
}


/*
 * Transmit Queue Initialization
 *
 * Allocate and initialize the host-resident transmit queue structures
 * and then initialize the CP-resident queue structures.
 * 
 * Called at interrupt level.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
void
fore_xmit_initialize(fup)
	Fore_unit	*fup;
{
	Aali		*aap = fup->fu_aali;
	Xmit_queue	*cqp;
	H_xmit_queue	*hxp;
	Q_status	*qsp;
	Q_status	*qsp_dma;
	int		i;

	/*
	 * Point to CP-resident transmit queue
	 */
	cqp = (Xmit_queue *)(fup->fu_ram + CP_READ(aap->aali_xmit_q));

	/*
	 * Point to host-resident transmit queue structures
	 */
	hxp = fup->fu_xmit_q;
	qsp = fup->fu_xmit_stat;
	qsp_dma = fup->fu_xmit_statd;

	/*
	 * Loop thru all queue entries and do whatever needs doing
	 */
	for (i = 0; i < XMIT_QUELEN; i++) {

		/*
		 * Set queue status word to free
		 */
		*qsp = QSTAT_FREE;

		/*
		 * Set up host queue entry and link into ring
		 */
		hxp->hxq_cpelem = cqp;
		hxp->hxq_status = qsp;
		if (i == (XMIT_QUELEN - 1))
			hxp->hxq_next = fup->fu_xmit_q;
		else
			hxp->hxq_next = hxp + 1;

		/*
		 * Now let the CP into the game
		 */
		cqp->cq_status = (CP_dma) CP_WRITE(qsp_dma);

		/*
		 * Bump all queue pointers
		 */
		hxp++;
		qsp++;
		qsp_dma++;
		cqp++;
	}

	/*
	 * Initialize queue pointers
	 */
	fup->fu_xmit_head = fup->fu_xmit_tail = fup->fu_xmit_q;

	return;
}


/*
 * Drain Transmit Queue
 *
 * This function will free all completed entries at the head of the
 * transmit queue.  Freeing the entry includes releasing the transmit
 * buffers (buffer chain) back to the kernel.  
 *
 * May be called in interrupt state.
 * Must be called with interrupts locked out.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
void
fore_xmit_drain(fup)
	Fore_unit	*fup;
{
	H_xmit_queue	*hxp;
	H_dma		*sdmap;
	Fore_vcc	*fvp;
	struct vccb	*vcp;
	KBuffer		*m;

	/*
	 * Process each completed entry
	 */
	while (*fup->fu_xmit_head->hxq_status & QSTAT_COMPLETED) {

		hxp = fup->fu_xmit_head;

		/*
		 * Release the entry's DMA addresses and buffer chain
		 */
		for (m = hxp->hxq_buf, sdmap = hxp->hxq_dma; m;
				m = KB_NEXT(m), sdmap++) {
			caddr_t		cp;

			KB_DATASTART(m, cp, caddr_t);
			DMA_FREE_ADDR(cp, *sdmap, KB_LEN(m), 0);
		}
		KB_FREEALL(hxp->hxq_buf);

		/*
		 * Get VCC over which data was sent (may be null if
		 * VCC has been closed in the meantime)
		 */
		fvp = hxp->hxq_vcc;

		/*
		 * Now collect some statistics
		 */
		if (*hxp->hxq_status & QSTAT_ERROR) {
			/*
			 * CP ran into problems, not much we can do
			 * other than record the event
			 */
			fup->fu_pif.pif_oerrors++;
			if (fvp) {
				vcp = fvp->fv_connvc->cvc_vcc;
				vcp->vc_oerrors++;
				if (vcp->vc_nif)
					vcp->vc_nif->nif_if.if_oerrors++;
			}
		} else {
			/*
			 * Good transmission
			 */
			int	len = XDS_GET_LEN(hxp->hxq_descr->xd_spec);

			fup->fu_pif.pif_opdus++;
			fup->fu_pif.pif_obytes += len;
			if (fvp) {
				vcp = fvp->fv_connvc->cvc_vcc;
				vcp->vc_opdus++;
				vcp->vc_obytes += len;
				if (vcp->vc_nif) {
					vcp->vc_nif->nif_obytes += len;
					vcp->vc_nif->nif_if.if_opackets++;
#if (defined(BSD) && (BSD >= 199103))
					vcp->vc_nif->nif_if.if_obytes += len;
#endif
				}
			}
		}

		/*
		 * Mark this entry free for use and bump head pointer
		 * to the next entry in the queue
		 */
		*hxp->hxq_status = QSTAT_FREE;
		fup->fu_xmit_head = hxp->hxq_next;
	}

	return;
}


/*
 * Free Transmit Queue Data Structures
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
void
fore_xmit_free(fup)
	Fore_unit	*fup;
{
	H_xmit_queue	*hxp;
	H_dma		*sdmap;
	KBuffer		*m;
	int		i;

	/*
	 * Free any transmit buffers left on the queue
	 */
	if (fup->fu_flags & CUF_INITED) {
		while (*fup->fu_xmit_head->hxq_status != QSTAT_FREE) {

			hxp = fup->fu_xmit_head;

			/*
			 * Release the entry's DMA addresses and buffer chain
			 */
			for (m = hxp->hxq_buf, sdmap = hxp->hxq_dma; m;
					m = KB_NEXT(m), sdmap++) {
				caddr_t		cp;

				KB_DATASTART(m, cp, caddr_t);
				DMA_FREE_ADDR(cp, *sdmap, KB_LEN(m), 0);
			}
			KB_FREEALL(hxp->hxq_buf);

			*hxp->hxq_status = QSTAT_FREE;
			fup->fu_xmit_head = hxp->hxq_next;
		}
	}

	/*
	 * Free the status words
	 */
	if (fup->fu_xmit_stat) {
		if (fup->fu_xmit_statd) {
			DMA_FREE_ADDR(fup->fu_xmit_stat, fup->fu_xmit_statd,
				sizeof(Q_status) * XMIT_QUELEN,
				ATM_DEV_NONCACHE);
		}
		atm_dev_free((volatile void *)fup->fu_xmit_stat);
		fup->fu_xmit_stat = NULL;
		fup->fu_xmit_statd = NULL;
	}

	/*
	 * Free the transmit descriptors
	 */
	hxp = fup->fu_xmit_q;
	for (i = 0; i < XMIT_QUELEN; i++, hxp++) {

		/*
		 * Free the transmit descriptor for this queue entry
		 */
		if (hxp->hxq_descr_dma) {
			DMA_FREE_ADDR(hxp->hxq_descr, hxp->hxq_descr_dma,
				sizeof(Xmit_descr), 0);
			hxp->hxq_descr_dma = NULL;
		}

		if (hxp->hxq_descr) {
			atm_dev_free(hxp->hxq_descr);
			hxp->hxq_descr = NULL;
		}
	}

	return;
}

