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

/*
 * Efficient ENI Adapter Support
 * -----------------------------
 *
 * Receive management
 *
 */

#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 <net/if.h>
#include <net/netisr.h>
#include <netinet/in.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/hea/eni_stats.h>
#include <dev/hea/eni.h>
#include <dev/hea/eni_var.h>

#ifndef lint
__RCSID("@(#) $FreeBSD$");
#endif

static void	eni_recv_stack(void *, KBuffer *);

#ifdef	DIAGNOSTIC
extern int	eni_pdu_print;
#endif

/*
 * Procedure to remove VCs from the Service List and generate DMA
 * requests to move the associated PDUs into host memory. As PDUs
 * are completed in adapter memory, the adapter examines the IN_SERVICE
 * bit for the VC in the VC table. If this bit is not set, the adapter
 * will place the VC number at the end of the service list queue, set
 * the IN_SERVICE bit in the VC table, and interrupt the host. The host
 * will remove VCs from the service list, clear the IN_SERVICE bit in
 * the VC table, and create a DMA list to move the PDU into host buffers.
 *
 * Arguments:
 *	eup		pointer to per unit structure
 *
 * Returns:
 *	none
 *
 */
void
eni_do_service ( eup )
	Eni_unit *eup;
{
	int		vcc;
	Eni_vcc		*evp;
	u_long		servwrite;
	VCI_Table	*vct;
	u_long		rdptr;
	u_long		*rxp;
	KBuffer		*m;
	u_long		dma[TEMP_DMA_SIZE];
	u_long		i, j;
	u_long		dma_rd, dma_wr;
	u_long		dma_avail;
	int		pdulen;
	int		mask;
	u_long		*upp;

	/*
	 * Where is the adapter currently inserting entries?
	 */
	servwrite = eup->eu_midway[MIDWAY_SVCWR] & SVC_SIZE_MASK;
	/*
	 * As long as we're not caught up with the adapter, keep
	 * removing VCs from the service list.
	 */
	while ( servwrite != eup->eu_servread ) {
		int	vci_hdr;
		u_long	descr;

		/*
		 * Get VC number and find VC table entry.
		 */
		vcc = eup->eu_svclist[eup->eu_servread];
		vct = &eup->eu_vcitbl[vcc];
		vci_hdr = vct->vci_control;	/* Current status */

		/*
		 * Check that this VCC still needs servicing. We
		 * might have closed this VCC down in between
		 * the adapter setting the flag and our checking
		 * the flag. Also check that we haven't placed the
		 * VCC into TRASH mode.
		 */
		if ( ( vci_hdr & VCI_IN_SERVICE ) == 0 ||
		    ( (vci_hdr & ~VCI_MODE_MASK) ==
			(VCI_MODE_TRASH << VCI_MODE_SHIFT) ) )
			    goto next_vcc;

		/*
		 * Find the size of this VCs buffer
		 */
		mask = (vci_hdr >> VCI_SIZE_SHIFT) & VCI_SIZE_MASK;
		mask = 1 << (ENI_LOC_PREDIV + mask);
		/* Turn byte count into word count */
		mask >>= 2;
		/*
		 * Find the start of the adapter buffer for this VC.
		 */
		rxp = (u_long *)
		    ((intptr_t)(((vci_hdr >> VCI_LOC_SHIFT ) & VCI_LOC_MASK)
			<< ENI_LOC_PREDIV) + (intptr_t)eup->eu_ram);
		/*
		 * Locate incoming VCC for this PDU and find where we
		 * should next read from.
		 */
		evp = (Eni_vcc *) atm_dev_vcc_find ( (Cmn_unit *)eup,
		    0, vcc, VCC_IN );
		if ( evp == (Eni_vcc *)NULL )
			goto next_vcc;		/* VCI no longer active */
		rdptr = evp->ev_rxpos;
		/*
		 * Find out where the adapter is currently reassembling.
		 * The PDU which starts at descr is not yet complete so we
		 * must stop there.
		 */
		descr = ( vct->vci_descr >> 16 ) & 0x7FFF;
		/*
		 * As long as we haven't processed all the completed PDUs on
		 * this VC, keep going...
		 */
		while ( rdptr != descr )
		{
		    int		n_cells;
		    int		pdu_descr;
		    int		aal5;

		    /*
		     * Ensure that the following are reset for every new
		     * PDU.
		     */
		    upp = NULL;
		    m = NULL;

		    /*
		     * Fisrt build a DMA with JK to skip the descriptor word.
		     * We must always skip the descriptor even if it turns out
		     * that there isn't any PDU here.
		     */
		    j = 0;
		    dma[j++] = (((rdptr + 1) & (mask-1)) << DMA_COUNT_SHIFT ) |
		    	( vcc << DMA_VCC_SHIFT ) | DMA_JK;
		    dma[j++] = 0;

		    /*
		     * We'll use some of the values below for skipping
		     * bad PDUs or counting statistics so compute them
		     * now.
		     */

		    /*
		     * Grab a copy of the descriptor word
		     */
		    pdu_descr = rxp[rdptr];

		    /*
		     * Strip out cell count from descriptor word.
		     * At this point, we still don't know if there
		     * is any real data until after we check for
		     * TRASH mode.
		     */
		    n_cells = pdu_descr & DESCR_CELL_COUNT;

		    /*
		     * Is this an AAL5 PDU? Check MODE in vci_hdr.
		     */
		    aal5 = ( ( vci_hdr & ~VCI_MODE_MASK ) ==
			VCI_MODE_AAL5 << VCI_MODE_SHIFT );

		    /*
		     * Now check to see if we're trashing on this vcc.
		     * If so, there is no data with this VC and the
		     * next word after the current descriptor is the
		     * descriptor for the next PDU.
		     */
		    if ( ( pdu_descr & DESCR_TRASH_BIT ) != 0 ) {
			if ( aal5 )
				/*
				 * Count as number of AAL5 cells dropped
				 */
				eup->eu_stats.eni_st_aal5.aal5_drops += n_cells;
			else
				/*
				 * Count as number of AAL0 cells dropped
				 */
				eup->eu_stats.eni_st_aal0.aal0_drops += n_cells;
			eup->eu_pif.pif_ierrors++;
			/*
			 * When cells have been trashed, all we have in the
			 * buffer is a descriptor word. There are no data
			 * words. Set the number of cells to zero so that
			 * we correctly skip to the next word which will
			 * be the descriptor for the next PDU.
			 */
			n_cells = 0;
			/*
			 * Go issue the DMA to skip this descriptor word.
			 */
			goto send_dma;
		    }

		    /*
		     * Data length: number of cells * cell size
		     */
		    pdulen = n_cells * BYTES_PER_CELL;

		    /*
		     * If this is an AAL5 PDU, then we need to check
		     * for the presence of any CRC errors. If there
		     * is one or more CRC errors, then we are going to
		     * drop this PDU.
		     */
		    if ( aal5 && ( pdu_descr & DESCR_CRC_ERR ) ) {
			/*
			 * Count the stat
			 */
			eup->eu_pif.pif_ierrors++;
			eup->eu_stats.eni_st_aal5.aal5_pdu_crc++;
			if ( evp->ev_connvc->cvc_vcc )
				evp->ev_connvc->cvc_vcc->vc_ierrors++;
			/*
			 * Build a DMA entry to skip the rest of this
			 * PDU.
			 */
			dma[j++] =
			    (((rdptr + n_cells*WORDS_PER_CELL + 1)
				& (mask-1)) << DMA_COUNT_SHIFT ) |
				    (vcc << DMA_VCC_SHIFT ) | DMA_JK;
			dma[j++] = 0;
			/*
			 * All done with this PDU. Get a buffer to save some
			 * data for reclamation services.
			 */
			KB_ALLOCPKT ( m, ENI_SMALL_BSIZE, KB_F_NOWAIT,
			    KB_T_DATA );
			if ( m ) {
				u_long	*up;

				KB_DATASTART ( m, up, u_long * );
				/*
				 * Indicate no PDU
				 */
				KB_PLENSET ( m, 0 );
				/*
				 * Set buffer length - only driver overhead
				 */
				KB_LEN ( m ) = 3 * sizeof ( u_long );
				/*
				 * Insert vcc, space for DMA pointers,
				 * and pdulen
				 */
				*up++ = vcc;
				upp = up;	/* Remember location */
				up++;		/* And skip it */
						/* - to be filled later */
				*up = pdulen;	/* Actual PDU length if it */
						/* were valid */
			} else {
				/*
				 * We've a real problem here as now we can't
				 * reclaim/advance resources/safety pointers.
				 */
				eup->eu_stats.eni_st_drv.drv_rv_norsc++;
#ifdef	DO_LOG
				log ( LOG_ERR,
    "eni_do_service: No drain buffers available. Receiver about to lock.\n" );
#endif
			}
			goto send_dma;
		    }

		    /*
		     * Do we need to strip the AAL layer? Yes if this
		     * is an AAL5 PDU.
		     */
		    if ( aal5 ) {
			/*
			 * Grab the CS-PDU length. Find the address of the
			 * last word, back up one word to skip CRC, and
			 * then mask the whole thing to handle circular wraps.
			 */
			pdulen = rxp[(rdptr + n_cells*WORDS_PER_CELL - 1)
			    & (mask-1)]
				& 0xFFFF;
		    }

		    /*
		     * We now have a valid PDU of some length. Build
		     * the necessary DMA list to move it into host
		     * memory.
		     */

		    /*
		     * Get an initial buffer.
		     */
		    KB_ALLOCPKT ( m, ENI_SMALL_BSIZE, KB_F_NOWAIT, KB_T_DATA );
		    /*
		     * Do we have a valid buffer?
		     */
		    if ( m != (KBuffer *)NULL )
		    {
			int	len;
			u_long	*up;
			KBuffer *m0;
	
			KB_DATASTART ( m, up, u_long * );
			/*
			 * Fill in pdulen in PKTHDR structure (for IP).
			 */
			KB_PLENSET ( m, pdulen );
			/*
			 * We're going to save the VCI nuber, the start
			 * and stop DMA pointers, and the PDU length at
			 * the head of the buffer. We'll pull this out
			 * later after the DMA has completed.
			 *
			 * Insert VCI number as first word in first buffer,
			 * remeber where we want to store the start/stop
			 * pointers, and store the PDU length.
			 */
			*up++ = vcc;	/* PDU's VCC */
			upp = up;	/* Remember where we are */
			up++;		/* To stuff start/stop pointers in */
			*up++ = pdulen;	/* PDU's length */
			/*
			 * Leave some extra room in case a higher protocol
			 * (IP) wants to do a pullup. Maybe we can keep
			 * someone from having to allocate another buffer
			 * a do a larger memory copy.
			 */
			len = MIN ( ENI_SMALL_BSIZE, pdulen );
			(void) eni_set_dma ( eup, 1, dma, TEMP_DMA_SIZE, &j,
				vcc, (u_long)up, len );
			/*
			 * Adjust length of remaining data in PDU
			 */
			pdulen -= len;
			/*
			 * Set buffer length, including our overhead
			 */
			KB_LEN ( m ) = len + 3 * sizeof ( u_long );
			/*
			 * Finish by moving anything which won't fit in
			 * first buffer
			 */
			m0 = m;
			while ( pdulen ) {
				KBuffer *m1;
				u_long	data_addr;
	
				/*
				 * Get another buffer
				 */
				KB_ALLOCEXT ( m1, ENI_LARGE_BSIZE, KB_F_NOWAIT,
					KB_T_DATA );
	
				/*
				 * If we succeeded...
				 */
				if ( m1 ) {
				    /*
				     * Figure out how much we can move into
				     * this buffer.
				     */
				    len = MIN ( ENI_LARGE_BSIZE, pdulen );
				    /*
				     * Setup DMA list for this buffer
				     */
				    KB_DATASTART ( m1, data_addr, u_long );
				    (void) eni_set_dma
					( eup, 1, dma, TEMP_DMA_SIZE, &j, vcc,
					    data_addr, len );
				    /*
				     * Adjust remaining length
				     */
				    pdulen -= len;
				    /*
				     * Set buffer length
				     */
				    KB_LEN ( m1 ) = len;
				    /*
				     * Link new buffer onto end and advance
				     * pointer
				     */
				    KB_NEXT ( m0 ) = m1;
				    m0 = m1;
				} else {
				    /*
				     * Either we were unable to grab another
				     * buffer or there are no large buffers
				     * available. We know that the first
				     * buffer is valid, so drop everything
				     * else, build a JK DMA to skip/drop this
				     * PDU, set the pointers to reclaim
				     * resources/advance pointers, and
				     * finish this PDU now.
				     */
				    if ( KB_NEXT ( m ) )
				    	KB_FREEALL ( KB_NEXT ( m ) );
				    eup->eu_pif.pif_ierrors++;
				    j = 2;
				    dma[j++] =
				        (((rdptr + n_cells*WORDS_PER_CELL + 1)
					    & (mask-1)) << DMA_COUNT_SHIFT ) |
					        (vcc << DMA_VCC_SHIFT ) |
						    DMA_JK;
				    dma[j++] = 0;
				    /*
				     * Reset PDU length to zero
				     */
				    KB_PLENSET ( m, 0 );
				    /*
				     * Count some statistics
				     */
				    /*
				     * Count this as dropped cells
				     */
				    if ( aal5 ) {
					eup->eu_stats.eni_st_aal5.aal5_drops +=
					    n_cells;
					eup->eu_stats.eni_st_aal5.aal5_pdu_drops++;
				    } else
					eup->eu_stats.eni_st_aal0.aal0_drops +=
					    n_cells;
				    /*
				     * Drop it
				     */
				    goto send_dma;
				}
			}
			/*
			 * If necessary, skip AAL layer
			 */
			if ( aal5 ) {
				dma[j++] =
				  (((rdptr + n_cells*WORDS_PER_CELL + 1)
					& (mask-1)) << DMA_COUNT_SHIFT)
				            | (vcc << DMA_VCC_SHIFT) | DMA_JK;
				dma[j++] = 0;
			}
		    } else {
			/*
			 * We failed to get an initial buffer. Since we
			 * haven't changed anything for this PDU yet and the
			 * PDU is still valid, exit now and try to service it
			 * next time around. We're not very likely to get
			 * another buffer right now anyways.
			 */
			eup->eu_stats.eni_st_drv.drv_rv_nobufs++;
#ifdef	DO_LOG
			log ( LOG_ERR,
"eni_do_service: No buffers available. Exiting without servicing service list.\n" );
#endif
			/*
			 * Clear the IN_SERVICE indicator for this VCC
			 */
			vct->vci_control &= ~VCI_IN_SERVICE;
			return;
		    }

send_dma:
		    /*
		     * Set the end bit on the last DMA for this PDU
		     */
		    dma[j-2] |= DMA_END_BIT;

		    /*
		     * Where are the current DMA pointers
		     */
		    dma_rd = eup->eu_midway[MIDWAY_RX_RD];
		    dma_wr = eup->eu_midway[MIDWAY_RX_WR];

		    /*
		     * Check how much space is available
		     */
		    if ( dma_rd == dma_wr )
			dma_avail = DMA_LIST_SIZE;
		    else
			dma_avail = ( dma_rd + DMA_LIST_SIZE - dma_wr )
			    & (DMA_LIST_SIZE-1);

		    /*
		     * Check for queue full or wrap past write okay pointer
		     */
		    if ( dma_avail < j  ||
		        ( dma_wr + j > eup->eu_rxdmawr + DMA_LIST_SIZE ) ) {
			/*
			 * There's no room in the DMA list to insert
			 * this request. Since we haven't changed anything
			 * yet and the PDU is good, exit now and service
			 * it next time around. What we really need to do
			 * is wait for the RX list to drain and that won't
			 * happen if we keep trying to process PDUs here.
			 */
			eup->eu_stats.eni_st_drv.drv_rv_nodma++;
#ifdef	DO_LOG
			log ( LOG_ERR,
"eni_do_service: No room in receive DMA list. Postponing service request.\n" );
#endif
			/*
			 * Free the local buffer chain
			 */
			KB_FREEALL ( m );
			/*
			 * Clear the IN_SERVICE indicator for this VCC.
			 */
			vct->vci_control &= ~VCI_IN_SERVICE;
			return;	
		    }

		    /*
		     * If we have a buffer chain, save the starting
		     * dma_list location.
		     */
		    if ( upp ) {
			*upp = dma_wr << 16;
		    }

		    /*
		     * Stuff the DMA list
		     */
		    j >>= 1;
		    for ( i = 0; i < j; i++ ) {
			eup->eu_rxdma[dma_wr*2] = dma[i*2];
			eup->eu_rxdma[dma_wr*2+1] = dma[i*2+1];
			dma_wr = (dma_wr+1) & (DMA_LIST_SIZE-1);
		    }
		    /*
		     * If we have a buffer chain, save the location of
		     * the ending dma_list location and queue the chain
		     * so that we can recover the resources later.
		     */
		    if ( upp ) {
			*upp |= dma_wr;
		        /*
		         * Place buffer on receive queue waiting for RX_DMA
		         */
		        if ( _IF_QFULL ( &eup->eu_rxqueue ) ) {
			    /*
			     * We haven't done anything we can't back out
			     * of. Drop request and service it next time.
			     * We've inserted the DMA list but it's not
			     * valid until we advance the RX_WR pointer,
			     * thus it's okay to bail here...
			     */
			    eup->eu_stats.eni_st_drv.drv_rv_rxq++;
#ifdef	DO_LOG
			    log ( LOG_ERR,
	"eni_do_service: RX drain queue full. Postponing servicing.\n" );
#endif
			    KB_FREEALL ( m );
			    /*
			     * Clear the IN_SERVICE indicator for this VCC.
			     */
			    vct->vci_control &= ~VCI_IN_SERVICE;
			    return;
		        } else { 
		            _IF_ENQUEUE ( &eup->eu_rxqueue, m );
			    /*
			     * Advance the RX_WR pointer to cause
			     * the adapter to work on this DMA list.
			     */
		            eup->eu_midway[MIDWAY_RX_WR] = dma_wr;
		        }
		    }
		    /*
		     * Advance our notion of where the next PDU
		     * should start.
		     */
		    rdptr = (rdptr + n_cells*WORDS_PER_CELL + 1)
			& (mask-1);
		    evp->ev_rxpos = rdptr;

		    /*
		     * Increment cells/pdu received stats.
		     */
		    eup->eu_stats.eni_st_atm.atm_rcvd += n_cells;
		    if ( aal5 ) {
			eup->eu_stats.eni_st_aal5.aal5_rcvd += n_cells;
			eup->eu_stats.eni_st_aal5.aal5_pdu_rcvd++;
		    } else {
			eup->eu_stats.eni_st_aal0.aal0_rcvd += n_cells;
		    }

		    /*
		     * Continue processing PDUs on this same VCI
		     */
		}

next_vcc:
		/*
		 * Advance to next entry in the service_list.
		 */
		eup->eu_servread = (eup->eu_servread + 1) & SVC_SIZE_MASK;

		/*
		 * And clear the IN_SERVICE indicator for this VCC.
		 */
		vct->vci_control &= ~VCI_IN_SERVICE;
	}
	return;
}

/*
 * Drain Receive queue
 *
 * As we build DMA lists to move PDUs from adapter buffers into host
 * buffers, we place the request on a private ifqueue so that we can
 * free any resources AFTER we know they've been successfully DMAed.
 * As part of the service processing, we record the PDUs start and stop
 * entries in the DMA list, and prevent wrapping. When we pull the top
 * entry off, we simply check that the current DMA location is outside
 * this PDU and if so, it's okay to free things.
 *
 * Arguments:
 *	eup		pointer to device unit structure
 *
 * Returns:
 *	none
 *
 */
void
eni_recv_drain ( eup )
	Eni_unit *eup;
{
	KBuffer 	*m;
	Eni_vcc		*evp;
	struct vccb	*vcp;
	u_long		vcc;
	u_long		DMA_Rdptr;
	u_long		dma_wrp;
	u_long		start, stop;
	int		que = 0;
	int		s;

	s = splimp();
	/* Pop first buffer */
	_IF_DEQUEUE ( &eup->eu_rxqueue, m );
	while ( m ) {
		u_long	*up;
		u_long	pdulen;

		KB_DATASTART ( m, up, u_long * );

		/*
		 * Grab the VCI number
		 */
		vcc = *up++;

		/*
		 * Check to see if we can process this buffer yet.
		 */
		/* Get current DMA_Rdptr */
		DMA_Rdptr = eup->eu_midway[MIDWAY_RX_RD];
		/* Boundaries for first buffer */
		dma_wrp = *up++;
		start = dma_wrp >> 16;
		stop = dma_wrp & 0xffff;
		/*
		 * Start should not equal stop because that would
		 * mean we tried inserting a NULL DMA list.
		 */
		if ( start > stop ) {		/* We wrapped */
			if ( !(DMA_Rdptr >= stop && DMA_Rdptr < start) ) {
				_IF_PREPEND ( &eup->eu_rxqueue, m );
				goto finish;
			}
		} else {
			if ( DMA_Rdptr < stop && DMA_Rdptr >= start ) {
				_IF_PREPEND ( &eup->eu_rxqueue, m );
				goto finish;
			}
		}
		/*
		 * Adapter is finished with this buffer, we can
		 * continue processing it now.
		 */

		/*
		 * Locate incoming VCC for this PDU
		 */
		evp = (Eni_vcc *) atm_dev_vcc_find ( (Cmn_unit *)eup,
		    0, vcc, VCC_IN );

		if ( evp == NULL ) {
			eup->eu_stats.eni_st_drv.drv_rv_novcc++;
			KB_FREEALL ( m );
			goto next_buffer;
		}

#ifdef	DIAGNOSTIC
		if ( eni_pdu_print )
		    atm_dev_pdu_print ( (Cmn_unit *)eup, (Cmn_vcc *)evp, m,
		        "eni_stack_drain" );
#endif

		/*
		 * Grab theoretical PDU length
		 */
		pdulen = *up++;

		/*
		 * Quick, count the PDU
		 */
		eup->eu_pif.pif_ipdus++;
		eup->eu_pif.pif_ibytes += pdulen;
		if ( evp ) {
		    vcp = evp->ev_connvc->cvc_vcc;
		    if ( vcp ) {
			vcp->vc_ipdus++;
			vcp->vc_ibytes += pdulen;
			if ( vcp->vc_nif ) {
			    vcp->vc_nif->nif_ibytes += pdulen;
			    vcp->vc_nif->nif_if.if_ipackets++;
#if (defined(BSD) && (BSD >= 199103))
			    vcp->vc_nif->nif_if.if_ibytes += pdulen;
#endif
			}
		    }
		}

		/*
		 * Advance DMA write allowable pointer
		 */
		eup->eu_rxdmawr = stop;

		/*
		 * Get packet PDU length
		 */
		KB_PLENGET ( m, pdulen );

		/*
		 * Only try queueing this if there is data
		 * to be handed up to the next layer. Errors
		 * such as CRC and VC trashing will get us this
		 * far to advance pointers, etc., but the PDU
		 * length will be zero.
		 */
		if ( pdulen ) {
			/*
			 * We saved three words back in eni_do_service()
			 * to use for callback. Since the core only
			 * expects two words, skip over the first one.
			 * Then, reset up pointer to start of buffer data
			 * area and write the callback info.
			 */
			KB_HEADADJ ( m, -sizeof(u_long) );
			KB_DATASTART ( m, up, u_long * );
			*((int *)up) = (int)eni_recv_stack;
			up++;
			*((int *)up) = (int)(intptr_t)evp;
			/*
			 * Schedule callback
			 */
			if (IF_HANDOFF(&atm_intrq, m, NULL)) {
				que++;
			} else {
				eup->eu_stats.eni_st_drv.drv_rv_intrq++;
				eup->eu_pif.pif_ierrors++;
#ifdef	DO_LOG
				log ( LOG_ERR,
"eni_receive_drain: ATM_INTRQ is full. Unable to pass up stack.\n" );
#endif
			}
		} else {
			/*
			 * Free zero-length buffer
			 */
			KB_FREEALL(m);
		}

next_buffer:
		/*
		 * Look for next buffer
		 */
		_IF_DEQUEUE ( &eup->eu_rxqueue, m );
	}
finish:
	(void) splx(s);

	/*
	 * If we found any completed buffers, schedule a call into
	 * the kernel to process the atm_intrq.
	 */
	if ( que )
		schednetisr(NETISR_ATM);
	return;
}

/*
 * Pass incoming PDU up Stack
 *
 * This function is called via the core ATM interrupt queue callback
 * set in eni_recv_drain(). It will pass the supplied incoming
 * PDU up the incoming VCC's stack.
 *
 * Arguments:
 *	tok		token to identify stack instantiation
 *	m		pointer to incoming PDU buffer chain
 *
 * Returns:
 *	none
 */
static void
eni_recv_stack ( tok, m )
	void		*tok;
	KBuffer		*m;
{
	Eni_vcc		*evp = (Eni_vcc *)tok;
	int		err;

	/*
	 * This should never happen now but if it does and we don't stop it,
	 * we end up panic'ing in netatm when trying to pull a function
	 * pointer and token value out of a buffer with address zero.
	 */
	if ( !m ) {
#ifdef	DO_LOG
		log ( LOG_ERR,
			"eni_recv_stack: NULL buffer, tok = %p\n", tok );
#endif
		return;
	}

	/*
	 * Send the data up the stack
	 */
	STACK_CALL ( CPCS_UNITDATA_SIG, evp->ev_upper,
		(void *)evp->ev_toku, evp->ev_connvc, (intptr_t)m, 0, err );
	if ( err ) {
		KB_FREEALL ( m );
	}

	return;
}