/*********************************************************************** 
* NOTICE
* All files contained on this disk are subject to the licensing conditions
* issued by MOTOROLA Inc.
*
* All files are copyright 1993 by MOTOROLA Inc. 
************************************************************************/


/********************************************************
 * File:	ethernet_r.c
 *
 * Description:
 *	The driver routines for handling receive
 *	on an ETHERNET port.
 *
 * Routines:
 *	ethernet_rxfr
 *	ethernet_busy
 *
 * Author:
 *	Tamar Shamir
 ********************************************************/

#include "gct.h"
#include "types.h"
#include "mtypes.h"
#include "msg.h"
#include "ethernet.h"
#include "bss.h"
#include "quicc.h"
#include "status.h"


extern driver_trace(int type, int id, volatile int par);


/********************************************************
 * routine:	ethernet_rxfr
 *
 * description:
 *	This is reached after being sent a RX_IND mesage
 *	by the interrupt routine (interrupt on QUICC
 *	receiving an ETHERNET frame).
 *	The receive buffer table is flushed of all ETHERNET
 *	frames received.
 *	The ETHERNET receive address is checked and only
 *	frames with an address that should be directed
 *	to the station are sent to the port's upper layer.
 *	An address that is directed to the station is a
 *	group or individual addres that belongs to the port,
 *	or the group broadcast address.
 *	The message sent is only a 32-bit code (not an
 *	allocated MSG structure) and therefore must
 *	not be relm-ed. Its structure corresponds to
 *	the first 32-bits of a normal HDR structure.
 *
 * arguments:
 *	h		the event message received
 *
 * return code:
 *
 * side effects:
 *	Advance quicc_rbd each time a
 *	received buffer is collected.
 *	The rxfr interrupt must be re-enabled after
 *	all received frames have been collected.
 *
 ********************************************************/
ethernet_rxfr(h, child)
HDR *h;
CHILD *child;
{
	GCT *gct;
	register WORK_AREA *wa;
	register unsigned short status;		/* status of BD */
	register R_FRAME *r1, *r2;
			/* r1 = first r_frame in frame */
			/* r2 = current r_frame */
	register R_FRAME *r3;
			/* r3 = previous r_frame */
	register QUICC_BD *nextBD;	/* next BD in frame */
	int flen;	/* accumulative frame length */
	int plen;	/* previous frame length (till last buffer) */
	char eofr;	/* boolean: end of frame recognized */
	char bcount;	/* number of BDs in this frame */
	char addr_match;	/* address match */
	char promisc;	/* promiscuous mode */
	char broadcast_mode;	/* broadcast mode */
	char multi_ind_mode;	/* mutiple individual address */
	int crc;	/* number of bytes in crc */
	ENET_ADDR dst_addr;
	unsigned short scc_psmr;
	unsigned short max_fr_lng;
	unsigned short length;
	unsigned short hdr_len;

	GETGCT(gct);
	wa = (WORK_AREA *)child->work_area;

	/*
	 * clear event bit
	 * this clears indications of old (masked) events
	 */
	wa->regs->scc_scce = (ETHERNET_RXF | ETHERNET_BSY);

	/*
	 * if receive is disabled,
	 * return immediately
	 */
	if( child->flags & RX_DISABLE )
		if( child->flags & RX_ACTIVATE )
			child->flags &= ~RX_ACTIVATE;
		else
			return;

	if( !wa->rx_q->nextb ){
		wa->regs->scc_sccm |= ETHERNET_RXF;
		return;
	}

	/*
	 * rbd address next BD for receive
	 */
	nextBD = wa->quicc_rbd;
	status = nextBD->status;
	r1 = r2 = r3 = wa->rx_q;
	flen = 0;
	eofr = bcount = 0;
	addr_match = 0;
	scc_psmr = wa->regs->scc_psmr;
	promisc = scc_psmr & PRO;
	broadcast_mode = !(scc_psmr & BRO);
	multi_ind_mode = scc_psmr & IAM;

	while( !(status & R_E) ){
		/*
		 * first check that r2 has been
		 * linked to the rx BD table
		 */
		if( !(r2->flags & LINKED_TO_TABLE) )
			break;

		bcount++;

		/*
		 * if first is set on the not first
		 * frame, the last buffer(s) where lost (busy?)
		 * and r2 is now start of new frame
		 */
		if( (r1 != r2) && (status & R_F) ){
			r1->flen = plen;
			r1->hdr.status = RECEIVE_ERROR;
			r1->hdr.err_info = R_BUSY;
			r3->nextb = 0;
			wa->rerr_cnt++;
			driver_trace(RX_IND, h->id, (int)r1);
			/* send frame to upper layer */
			if( R_BUSY & ~wa->rxerr_mask )
				return_to_pool(r1, child);
			else
				if( (*gct->send)(child->upper, r1) )
					return_to_pool(r1, child);

			/*
			 * adjust accumulative
			 * frame lengths
			 */
			flen = nextBD->length;
			plen = 0;

			/* advance frame pointer */
			r1 = r2;
			eofr = 1;
		}


		/*
		 * For the first buffer in the frame:
		 *  - get the destination address and check if it is
		 *    directed to this station.
		 *  - get the frame length from the buffer, if it is
		 *    less then 64 this frame has been padded.
		 */
		if (r1 == r2) {
			ETHERENT_GET_ADDR(&dst_addr, r1->buf);
			/*
			 * if the address is a group address
			 */
			if( dst_addr.high_addr & GROUP_ADDR ) {
				/*
				 * broadcast address
				 */
				if( BROADCAST_ADDR(&dst_addr) ) {
					if( broadcast_mode )
						addr_match = 1;
				}
				/*
				 * find the address in the hash table
				 */
				else if ( find_grp_addr(&dst_addr, wa) )
					addr_match = 1;
			}
			/*
			 * if the address is an individual address and
			 * the QUICC receives multi individual addresses
			 * find the address is the hash table
			 */
			else if( multi_ind_mode ) {
				if ( find_ind_addr(&dst_addr, wa) )
					addr_match = 1;
			}
			else	addr_match = 1;

			length = (r1->buf[12] << 8) & 0xff00;
			length |= r1->buf[13];
		}

		/*
		 * update accumulative frame length
		 * on the last BD in a frame, the length
		 * is the length of the entire ETHERNET frame.
		 */
		plen = flen;
		if( status & R_L ){
			if( !(promisc || addr_match) )
				goto end_of_frame;
			flen = nextBD->length;

			/*
			 * If flen is equal to 64, then if the length
			 * that was fetched from the data buffer
			 * was less then 64 - header length, the
			 * frame has been padded.
			 */
			hdr_len = 14 +
				(wa->regs->scc_psmr & CRC32? 4: 2);
			if( (flen == 64) && ((length + hdr_len) < 64)
					&& !(status & R_ERROR) ) {
				flen = r1->blen = r1->flen =
							length + 14;
				/*
				 * If this short frame arrived on two
				 * BDs, the length of the last bd
				 * should be 0.
				 */
				if( r1 != r2 )
					r2->blen = r2->flen = 0;
				goto end_of_frame;
			}

			/*
			 * remove the CRC from valid frames
			 * (note that the CRC may be split between
			 * the last two buffers)
			 */
			if( !(status & R_ERROR) ){
				crc = (wa->regs->scc_psmr & CRC32? 4: 2);
				flen -= crc;
			}
			max_fr_lng = (promisc ? wa->enet_pram->maxd1 :
						wa->enet_pram->maxd2);
			if( flen > max_fr_lng )
				r2->blen = max_fr_lng - plen;
			else if( flen >= plen )
				/* CRC in last buffer */
				r2->blen = flen - plen;
			else {
				/* some of CRC in second last buffer */
				r2->blen = 0;
				r3->blen -= (plen - flen);
				/* release emptied buffer */
				r3->nextb = r2->nextb;
				r2->nextb = 0;
				return_to_pool(r2, child);
				r2 = r3;
			}
		} else {
			flen += nextBD->length;
			r2->blen = nextBD->length;
		}


		/*
		 * if end of frame ...
		 */
end_of_frame:
		if( status & R_L ){
			/*
			 * update status and length
			 * in new received frame,
			 * zero accumulative length
			 * for next frame
			 */
			r1->flen = flen;	/* frame total length */
			r3 = r2;
			r2 = r2->nextb;
			r3->nextb = 0;	/* no more buffs in this frame */

			/*
			 * prepare for next R_FRAME
			 */
			flen = 0;

			driver_trace(RX_IND, r1->hdr.id, (int)r1);
			/*
			 * pass r_frame to upper layer
			 * (subject to error mask).
			 * buffers are linked via nextb
			 * if the address does not match and the scc
			 * is not in the promiscuous mode,
			 * release the frame.
			 */
			if( status & R_ERROR ){
				driver_trace(RX_ERROR, h->id, (int)nextBD);
				r1->hdr.status = RECEIVE_ERROR;
				r1->hdr.err_info = status & R_ERROR;
				wa->rerr_cnt++;
				if( (status&R_ERROR) & ~wa->rxerr_mask )
					return_to_pool(r1, child);
				else
					if( (*gct->send)(child->upper, r1) )
						return_to_pool(r1, child);
			} else if( !(promisc || addr_match) )
				return_to_pool(r1, child);
			else if( (*gct->send)(child->upper, r1) )
					return_to_pool(r1, child);
			r1 = r3 = r2;
			wa->rcount++;
			eofr = 1;
		} else {
			r3 = r2;
			r2 = r2->nextb;	/* next R_FRAME for rx */
		}

		/*
		 * advance rbd (to next Rx BD in table)
		 */
		if( status & R_W )
			nextBD = RBD_ADDR(wa);
		else
			nextBD++;

		if( eofr ){
			wa->rfree += bcount;
			eofr = bcount = 0;
			wa->quicc_rbd = nextBD;
		}

		status = nextBD->status;	/* update status */
	}

	wa->rx_q = r1;

	/*
	 * replenish the rx BD table
	 */
	status = replenish_rxbd(child);
	if( status ){
		MSG *m;
		extern MSG *getm();

		driver_trace(BUSY_IND, h->id, status);
		m = getm(BUSY_IND, h->id, 0, 0);
		if( m ){
			m->hdr.status = status;
			if( (*gct->send)( child->upper, m) )
				relm((HDR *)m);
		}
		/*
		 * enable BUSY interrupts if
		 * local busy was just cleared
		 */
		if( status == LOCAL_BUSY_CLEARED )
			wa->regs->scc_sccm |= ETHERNET_BSY;
	}

	/*
	 * re-enable interrupts
	 * setting the mask will result in an interrupt
	 * if any frames were received during the time
	 * the mask was zero. therefore, frames will not
	 * go unnoticed if received after exiting the while
	 * loop above and before the mask bit is set.
	 */
	wa->regs->scc_sccm |= ETHERNET_RXF;
}


/********************************************************
 * routine:	ethernet_busy
 *
 * description:
 *	Notify the upper layer that the
 *	busy condition has been encountered.
 *
 * arguments:
 *	h		points to dummy (32 bit) header
 *	child		child structure
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
ethernet_busy(h, child)
HDR *h;
CHILD *child;
{
	GCT *gct;
	MSG *m;
	extern MSG *getm();

	/*
	 * mark local busy
	 * (checked when returning to pool)
	 */
	child->flags |= LOCAL_BUSY;

	/*
	 * NOTE: this should be redundant
	 */
	ethernet_rxfr(h, child);

	/*
	 * if still busy, notify upper layer
	 */
	if( child->flags & (LOCAL_BUSY | POOL_EMPTY) ){
		GETGCT(gct);
		driver_trace(BUSY_IND, h->id, LOCAL_BUSY_DETECTED);
		m = getm(BUSY_IND, h->id, 0, 0);
		if( m ){
			m->hdr.status = LOCAL_BUSY_DETECTED;
			if( (*gct->send)(child->upper, m) )
				relm((HDR *)m);
		}
	}
}
