/*-
 * ===================================
 * 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.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/hfa/fore_buffer.c,v 1.15 2004/02/21 19:08:23 harti Exp $");

/*
 * FORE Systems 200-Series Adapter Support
 * ---------------------------------------
 *
 * Buffer Supply queue management
 *
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.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_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_buffer.c,v 1.15 2004/02/21 19:08:23 harti Exp $");
#endif


/*
 * Local functions
 */
static void	fore_buf_drain(Fore_unit *);
static void	fore_buf_supply_1s(Fore_unit *);
static void	fore_buf_supply_1l(Fore_unit *);


/*
 * Allocate Buffer Supply Queues Data Structures
 *
 * Here we are allocating memory for both Strategy 1 Small and Large
 * structures contiguously.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	0		allocations successful
 *	else		allocation failed
 */
int
fore_buf_allocate(fup)
	Fore_unit	*fup;
{
	caddr_t		memp;
	vm_paddr_t	pmemp;

	/*
	 * Allocate non-cacheable memory for buffer supply status words
	 */
	memp = atm_dev_alloc(
			sizeof(Q_status) * (BUF1_SM_QUELEN + BUF1_LG_QUELEN),
			QSTAT_ALIGN, ATM_DEV_NONCACHE);
	if (memp == NULL) {
		return (1);
	}
	fup->fu_buf1s_stat = (Q_status *) memp;
	fup->fu_buf1l_stat = ((Q_status *) memp) + BUF1_SM_QUELEN;

	pmemp = vtophys(fup->fu_buf1s_stat);
	if (pmemp == 0) {
		return (1);
	}
	fup->fu_buf1s_statd = pmemp;

	pmemp = vtophys(fup->fu_buf1l_stat);
	if (pmemp == 0) {
		return (1);
	}
	fup->fu_buf1l_statd = pmemp;

	/*
	 * Allocate memory for buffer supply descriptors
	 */
	memp = atm_dev_alloc(sizeof(Buf_descr) * 
			((BUF1_SM_QUELEN * BUF1_SM_ENTSIZE) + 
			 (BUF1_LG_QUELEN * BUF1_LG_ENTSIZE)),
			BUF_DESCR_ALIGN, 0);
	if (memp == NULL) {
		return (1);
	}
	fup->fu_buf1s_desc = (Buf_descr *) memp;
	fup->fu_buf1l_desc = ((Buf_descr *) memp) + 
			(BUF1_SM_QUELEN * BUF1_SM_ENTSIZE);

	pmemp = vtophys(fup->fu_buf1s_desc);
	if (pmemp == 0) {
		return (1);
	}
	fup->fu_buf1s_descd = pmemp;

	pmemp = vtophys(fup->fu_buf1l_desc);
	if (pmemp == 0) {
		return (1);
	}
	fup->fu_buf1l_descd = pmemp;

	return (0);
}


/*
 * Buffer Supply Queues Initialization
 *
 * Allocate and initialize the host-resident buffer supply 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_buf_initialize(fup)
	Fore_unit	*fup;
{
	Aali		*aap = fup->fu_aali;
	Buf_queue	*cqp;
	H_buf_queue	*hbp;
	Buf_descr	*bdp;
	vm_paddr_t	bdp_dma;
	Q_status	*qsp;
	vm_paddr_t	qsp_dma;
	int		i;

	/*
	 * Initialize Strategy 1 Small Queues
	 */

	/*
	 * Point to CP-resident buffer supply queue
	 */
	cqp = (Buf_queue *)(fup->fu_ram + CP_READ(aap->aali_buf1s_q));

	/*
	 * Point to host-resident buffer supply queue structures
	 */
	hbp = fup->fu_buf1s_q;
	qsp = fup->fu_buf1s_stat;
	qsp_dma = fup->fu_buf1s_statd;
	bdp = fup->fu_buf1s_desc;
	bdp_dma = fup->fu_buf1s_descd;

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

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

		/*
		 * Set up host queue entry and link into ring
		 */
		hbp->hbq_cpelem = cqp;
		hbp->hbq_status = qsp;
		hbp->hbq_descr = bdp;
		hbp->hbq_descr_dma = bdp_dma;
		if (i == (BUF1_SM_QUELEN - 1))
			hbp->hbq_next = fup->fu_buf1s_q;
		else
			hbp->hbq_next = hbp + 1;

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

		/*
		 * Bump all queue pointers
		 */
		hbp++;
		qsp++;
		qsp_dma += sizeof(Q_status);
		bdp += BUF1_SM_ENTSIZE;
		bdp_dma += BUF1_SM_ENTSIZE * sizeof(Buf_descr);
		cqp++;
	}

	/*
	 * Initialize queue pointers
	 */
	fup->fu_buf1s_head = fup->fu_buf1s_tail = fup->fu_buf1s_q;


	/*
	 * Initialize Strategy 1 Large Queues
	 */

	/*
	 * Point to CP-resident buffer supply queue
	 */
	cqp = (Buf_queue *)(fup->fu_ram + CP_READ(aap->aali_buf1l_q));

	/*
	 * Point to host-resident buffer supply queue structures
	 */
	hbp = fup->fu_buf1l_q;
	qsp = fup->fu_buf1l_stat;
	qsp_dma = fup->fu_buf1l_statd;
	bdp = fup->fu_buf1l_desc;
	bdp_dma = fup->fu_buf1l_descd;

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

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

		/*
		 * Set up host queue entry and link into ring
		 */
		hbp->hbq_cpelem = cqp;
		hbp->hbq_status = qsp;
		hbp->hbq_descr = bdp;
		hbp->hbq_descr_dma = bdp_dma;
		if (i == (BUF1_LG_QUELEN - 1))
			hbp->hbq_next = fup->fu_buf1l_q;
		else
			hbp->hbq_next = hbp + 1;

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

		/*
		 * Bump all queue pointers
		 */
		hbp++;
		qsp++;
		qsp_dma += sizeof(Q_status);
		bdp += BUF1_LG_ENTSIZE;
		bdp_dma += BUF1_LG_ENTSIZE * sizeof(Buf_descr);
		cqp++;
	}

	/*
	 * Initialize queue pointers
	 */
	fup->fu_buf1l_head = fup->fu_buf1l_tail = fup->fu_buf1l_q;

	return;
}


/*
 * Supply Buffers to CP
 *
 * This function will resupply the CP with buffers to be used to
 * store incoming data.
 *
 * May be called in interrupt state.
 * Must be called with interrupts locked out.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
void
fore_buf_supply(fup)
	Fore_unit	*fup;
{

	/*
	 * First, clean out the supply queues
	 */
	fore_buf_drain(fup);

	/*
	 * Then, supply the buffers for each queue
	 */
	fore_buf_supply_1s(fup);
	fore_buf_supply_1l(fup);

	return;
}


/*
 * Supply Strategy 1 Small Buffers to CP
 *
 * May be called in interrupt state.
 * Must be called with interrupts locked out.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
static void
fore_buf_supply_1s(fup)
	Fore_unit	*fup;
{
	H_buf_queue	*hbp;
	Buf_queue	*cqp;
	Buf_descr	*bdp;
	Buf_handle	*bhp;
	KBuffer		*m;
	int		nvcc, nbuf, i;

	/*
	 * Figure out how many buffers we should be giving to the CP.
	 * We're basing this calculation on the current number of open
	 * VCCs thru this device, with certain minimum and maximum values
	 * enforced.  This will then allow us to figure out how many more 
	 * buffers we need to supply to the CP.  This will be rounded up 
	 * to fill a supply queue entry.
	 */
	nvcc = MAX(fup->fu_open_vcc, BUF_MIN_VCC);
	nbuf = nvcc * 4;
	nbuf = MIN(nbuf, BUF1_SM_CPPOOL);
	nbuf -= fup->fu_buf1s_cnt;
	nbuf = roundup(nbuf, BUF1_SM_ENTSIZE);

	/*
	 * OK, now supply the buffers to the CP
	 */
	while (nbuf > 0) {

		/*
		 * Acquire a supply queue entry
		 */
		hbp = fup->fu_buf1s_tail;
		if (!((*hbp->hbq_status) & QSTAT_FREE))
			break;
		bdp = hbp->hbq_descr;

		/*
		 * Get a buffer for each descriptor in the queue entry
		 */
		for (i = 0; i < BUF1_SM_ENTSIZE; i++, bdp++) {
			caddr_t		cp;

			/*
			 * Get a small buffer
			 */
			KB_ALLOCPKT(m, BUF1_SM_SIZE, KB_F_NOWAIT, KB_T_DATA);
			if (m == 0) {
				break;
			}
			KB_HEADSET(m, BUF1_SM_DOFF);

			/*
			 * Point to buffer handle structure
			 */
			bhp = (Buf_handle *)((caddr_t)m + BUF1_SM_HOFF);
			bhp->bh_type = BHT_S1_SMALL;

			/*
			 * Setup buffer descriptor
			 */
			bdp->bsd_handle = bhp;
			KB_DATASTART(m, cp, caddr_t);
			bhp->bh_dma = bdp->bsd_buffer = vtophys(cp);
			if (bdp->bsd_buffer == 0) {
				/*
				 * Unable to assign dma address - free up
				 * this descriptor's buffer
				 */
				fup->fu_stats->st_drv.drv_bf_segdma++;
				KB_FREEALL(m);
				break;
			}

			/*
			 * All set, so queue buffer (handle)
			 */
			ENQUEUE(bhp, Buf_handle, bh_qelem, fup->fu_buf1s_bq);
		}

		/*
		 * If we we're not able to fill all the descriptors for
		 * an entry, free up what's been partially built
		 */
		if (i != BUF1_SM_ENTSIZE) {
			caddr_t		cp;

			/*
			 * Clean up each used descriptor
			 */
			for (bdp = hbp->hbq_descr; i; i--, bdp++) {

				bhp = bdp->bsd_handle;

				DEQUEUE(bhp, Buf_handle, bh_qelem, 
					fup->fu_buf1s_bq);

				m = (KBuffer *)
					((caddr_t)bhp - BUF1_SM_HOFF);
				KB_DATASTART(m, cp, caddr_t);
				KB_FREEALL(m);
			}
			break;
		}

		/*
		 * Finally, we've got an entry ready for the CP.
		 * So claim the host queue entry and setup the CP-resident
		 * queue entry.  The CP will (potentially) grab the supplied
		 * buffers when the descriptor pointer is set.
		 */
		fup->fu_buf1s_tail = hbp->hbq_next;
		(*hbp->hbq_status) = QSTAT_PENDING;
		cqp = hbp->hbq_cpelem;
		cqp->cq_descr = (CP_dma) CP_WRITE((u_long)hbp->hbq_descr_dma);

		/*
		 * Update counters, etc for supplied buffers
		 */
		fup->fu_buf1s_cnt += BUF1_SM_ENTSIZE;
		nbuf -= BUF1_SM_ENTSIZE;
	}

	return;
}


/*
 * Supply Strategy 1 Large Buffers to CP
 *
 * May be called in interrupt state.
 * Must be called with interrupts locked out.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
static void
fore_buf_supply_1l(fup)
	Fore_unit	*fup;
{
	H_buf_queue	*hbp;
	Buf_queue	*cqp;
	Buf_descr	*bdp;
	Buf_handle	*bhp;
	KBuffer		*m;
	int		nvcc, nbuf, i;

	/*
	 * Figure out how many buffers we should be giving to the CP.
	 * We're basing this calculation on the current number of open
	 * VCCs thru this device, with certain minimum and maximum values
	 * enforced.  This will then allow us to figure out how many more 
	 * buffers we need to supply to the CP.  This will be rounded up 
	 * to fill a supply queue entry.
	 */
	nvcc = MAX(fup->fu_open_vcc, BUF_MIN_VCC);
	nbuf = nvcc * 4 * RECV_MAX_SEGS;
	nbuf = MIN(nbuf, BUF1_LG_CPPOOL);
	nbuf -= fup->fu_buf1l_cnt;
	nbuf = roundup(nbuf, BUF1_LG_ENTSIZE);

	/*
	 * OK, now supply the buffers to the CP
	 */
	while (nbuf > 0) {

		/*
		 * Acquire a supply queue entry
		 */
		hbp = fup->fu_buf1l_tail;
		if (!((*hbp->hbq_status) & QSTAT_FREE))
			break;
		bdp = hbp->hbq_descr;

		/*
		 * Get a buffer for each descriptor in the queue entry
		 */
		for (i = 0; i < BUF1_LG_ENTSIZE; i++, bdp++) {
			caddr_t		cp;

			/*
			 * Get a cluster buffer
			 */
			KB_ALLOCEXT(m, BUF1_LG_SIZE, KB_F_NOWAIT, KB_T_DATA);
			if (m == 0) {
				break;
			}
			KB_HEADSET(m, BUF1_LG_DOFF);

			/*
			 * Point to buffer handle structure
			 */
			bhp = (Buf_handle *)((caddr_t)m + BUF1_LG_HOFF);
			bhp->bh_type = BHT_S1_LARGE;

			/*
			 * Setup buffer descriptor
			 */
			bdp->bsd_handle = bhp;
			KB_DATASTART(m, cp, caddr_t);
			bhp->bh_dma = bdp->bsd_buffer = vtophys(cp);
			if (bdp->bsd_buffer == 0) {
				/*
				 * Unable to assign dma address - free up
				 * this descriptor's buffer
				 */
				fup->fu_stats->st_drv.drv_bf_segdma++;
				KB_FREEALL(m);
				break;
			}

			/*
			 * All set, so queue buffer (handle)
			 */
			ENQUEUE(bhp, Buf_handle, bh_qelem, fup->fu_buf1l_bq);
		}

		/*
		 * If we we're not able to fill all the descriptors for
		 * an entry, free up what's been partially built
		 */
		if (i != BUF1_LG_ENTSIZE) {
			caddr_t		cp;

			/*
			 * Clean up each used descriptor
			 */
			for (bdp = hbp->hbq_descr; i; i--, bdp++) {
				bhp = bdp->bsd_handle;

				DEQUEUE(bhp, Buf_handle, bh_qelem, 
					fup->fu_buf1l_bq);

				m = (KBuffer *)
					((caddr_t)bhp - BUF1_LG_HOFF);
				KB_DATASTART(m, cp, caddr_t);
				KB_FREEALL(m);
			}
			break;
		}

		/*
		 * Finally, we've got an entry ready for the CP.
		 * So claim the host queue entry and setup the CP-resident
		 * queue entry.  The CP will (potentially) grab the supplied
		 * buffers when the descriptor pointer is set.
		 */
		fup->fu_buf1l_tail = hbp->hbq_next;
		(*hbp->hbq_status) = QSTAT_PENDING;
		cqp = hbp->hbq_cpelem;
		cqp->cq_descr = (CP_dma) CP_WRITE((u_long)hbp->hbq_descr_dma);

		/*
		 * Update counters, etc for supplied buffers
		 */
		fup->fu_buf1l_cnt += BUF1_LG_ENTSIZE;
		nbuf -= BUF1_LG_ENTSIZE;
	}

	return;
}


/*
 * Drain Buffer Supply Queues
 *
 * This function will free all completed entries at the head of each
 * buffer supply queue.  Since we consider the CP to "own" the buffers
 * once we put them on a supply queue and since a completed supply queue 
 * entry is only telling us that the CP has accepted the buffers that we 
 * gave to it, there's not much to do here.
 *
 * May be called in interrupt state.
 * Must be called with interrupts locked out.
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
static void
fore_buf_drain(fup)
	Fore_unit	*fup;
{
	H_buf_queue	*hbp;

	/*
	 * Drain Strategy 1 Small Queue
	 */

	/*
	 * Process each completed entry
	 */
	while (*fup->fu_buf1s_head->hbq_status & QSTAT_COMPLETED) {

		hbp = fup->fu_buf1s_head;

		if (*hbp->hbq_status & QSTAT_ERROR) {
			/*
			 * XXX - what does this mean???
			 */
			log(LOG_ERR, "fore_buf_drain: buf1s queue error\n");
		}

		/*
		 * Mark this entry free for use and bump head pointer
		 * to the next entry in the queue
		 */
		*hbp->hbq_status = QSTAT_FREE;
		fup->fu_buf1s_head = hbp->hbq_next;
	}


	/*
	 * Drain Strategy 1 Large Queue
	 */

	/*
	 * Process each completed entry
	 */
	while (*fup->fu_buf1l_head->hbq_status & QSTAT_COMPLETED) {

		hbp = fup->fu_buf1l_head;

		if (*hbp->hbq_status & QSTAT_ERROR) {
			/*
			 * XXX - what does this mean???
			 */
			log(LOG_ERR, "fore_buf_drain: buf1l queue error\n");
		}

		/*
		 * Mark this entry free for use and bump head pointer
		 * to the next entry in the queue
		 */
		*hbp->hbq_status = QSTAT_FREE;
		fup->fu_buf1l_head = hbp->hbq_next;
	}

	return;
}


/*
 * Free Buffer Supply Queue Data Structures
 *
 * Arguments:
 *	fup		pointer to device unit structure
 *
 * Returns:
 *	none
 */
void
fore_buf_free(fup)
	Fore_unit	*fup;
{
	Buf_handle	*bhp;
	KBuffer		*m;

	/*
	 * Free any previously supplied and not returned buffers
	 */
	if (fup->fu_flags & CUF_INITED) {

		/*
		 * Run through Strategy 1 Small queue
		 */
		while ((bhp = Q_HEAD(fup->fu_buf1s_bq, Buf_handle)) != NULL) {
			caddr_t		cp;

			/*
			 * Back off to buffer
			 */
			m = (KBuffer *)((caddr_t)bhp - BUF1_SM_HOFF);

			/*
			 * Dequeue handle and free buffer
			 */
			DEQUEUE(bhp, Buf_handle, bh_qelem, fup->fu_buf1s_bq);

			KB_DATASTART(m, cp, caddr_t);
			KB_FREEALL(m);
		}

		/*
		 * Run through Strategy 1 Large queue
		 */
		while ((bhp = Q_HEAD(fup->fu_buf1l_bq, Buf_handle)) != NULL) {
			caddr_t		cp;

			/*
			 * Back off to buffer
			 */
			m = (KBuffer *)((caddr_t)bhp - BUF1_LG_HOFF);

			/*
			 * Dequeue handle and free buffer
			 */
			DEQUEUE(bhp, Buf_handle, bh_qelem, fup->fu_buf1l_bq);

			KB_DATASTART(m, cp, caddr_t);
			KB_FREEALL(m);
		}
	}

	/*
	 * Free the status words
	 */
	if (fup->fu_buf1s_stat) {
		atm_dev_free((volatile void *)fup->fu_buf1s_stat);
		fup->fu_buf1s_stat = NULL;
		fup->fu_buf1s_statd = 0;
		fup->fu_buf1l_stat = NULL;
		fup->fu_buf1l_statd = 0;
	}

	/*
	 * Free the transmit descriptors
	 */
	if (fup->fu_buf1s_desc) {
		atm_dev_free(fup->fu_buf1s_desc);
		fup->fu_buf1s_desc = NULL;
		fup->fu_buf1s_descd = 0;
		fup->fu_buf1l_desc = NULL;
		fup->fu_buf1l_descd = 0;
	}

	return;
}

