/*=====================================================================

                      The Microcom MNP Library
        				(Microsoft C Version)

-----------------------------------------------------------------------

        LDRIVER - MNP Link RS-232 Interrupt Service Routines

     This module implements asynchronous I/O for IBM PC-compatible
     machines and thus much of this code may not be portable to 
     other dissimilar environments.

     This code implements Class 2 of the MNP link protocol,
     i.e. it uses the stop/start byte-oriented framing technique.
     A Class 3 implementation is not possible in the IBM PC 
     environment since the communications hardware is not capable
     of switching to synchronous (bit-oriented) operation.
     Protocol messages and procedures, however, are independent
     of the framining technique.		

     Global routines:

	- ascode: service I/O interrupt 
	- lne_stat: check carrier status 
	- trigger_sf: initiate transmit interrupts

=====================================================================*/

/* Header files
*/
#include <dos.h>
#include <mnpdat.h>

/* Interrupt-related definitions
*/
#define INT_8259A_PORT	0x20
#define EOI		0x20
#define RCV_DATA	4
#define THR_EMPTY	2
#define MODSTAT_CHNG	0

/* Send framer state names
*/
#define SF_INIT 	0
#define SF_DLE		1
#define SF_STX		2
#define SF_INFO 	3
#define SF_DLEINFO	4
#define SF_ETX		5
#define SF_FCS1 	6
#define SF_FCS2 	7

/* Receive framer state names
*/
#define RF_INIT 	0
#define RF_DLE		1
#define RF_STX		2
#define RF_HDLEN	3
#define RF_HEADER	4
#define RF_INFOL	5
#define RF_INFO 	6
#define RF_FCS1 	7
#define RF_FCS2 	8

/* Frame octet definitions
*/
#define SYN		0x16
#define DLE		0x10
#define ETX		3
#define STX		2

/* Length of header buffer (maximum acceptable LPDU header)
*/
#define MAX_HDRLEN	100

/* Bit set, clear and test definitions
*/
#define SETBIT1(bit)	lcb.status_1 |= bit;	   
#define SETBIT2(bit)	lcb.status_2 |= bit;
#define SETBIT3(bit)	lcb.status_3 |= bit;
#define CLRBIT1(bit)	lcb.status_1 &= ~bit;	     
#define CLRBIT2(bit)	lcb.status_2 &= ~bit;
#define CLRBIT3(bit)	lcb.status_3 &= ~bit;
#define BIT1SET(bit)	(lcb.status_1 & bit)	     
#define BIT2SET(bit)	(lcb.status_2 & bit)
#define BIT3SET(bit)	(lcb.status_3 & bit)

/* External references
*/
extern int iir_add;			/* int id reg address */
extern char linestat;		/* line status var */
extern int fcw_tm;			/* window timer */
extern struct link_ctl_blk lcb;  /* link control block */
extern struct buffer rb, ftb, rlkb;  /* buffers */
extern char *rb_iptr;		/* rcv buf insert pointer */
extern int rb_cnt,tbcnt;		/* buffer counts */
extern char rbuf[RBUF_LEN];	/* receive buffer */
extern int ln_tm;

/* Function definitions
*/
void snd_framer();
void rcv_framer();
void mod_stat();
void rcv_lt();
void rcv_la();
void rcv_lna();
void rcv_ln();

/* Local data
*/
char *sf_ptr,
     *rf_hptr,
     *rf_dptr;
char snd_char,
     rcv_char;
struct 
	{
	char low;
	char hi;
	}
		snd_fcs,			/* send fcs */
  		rcv_fcs,			/* calculated receive fcs */
  		rfcs;			/* received fcs */
unsigned char rdle_flg;		/* 'previous char DLE' flag */
int rf_hdrlen,				/* receive header length  */
    hdrcnt,			
    rf_datlen,
    sf_state,				/* send framer state var */
    rf_state,				/* receive framer state var */
    sf_len,
    sf_busy,				/* 'send frame' flag */
    sf_lt,				/* 'sending LT' flag */
    frame_snt,				/* 'info field sent' flag */ 
    frame_rcvd,				/* 'frame received' flag */
    frame_dne;				/* 'frame sent' flag */
unsigned char *rf_tiptr;

struct mnp_cb *p_mnpcb;
struct blst *dat_struct, *hdr_struct;

unsigned char modem_out_busy;	/* RS-232 hardware status flag
					          0=transmitter not active
					          1=transmitter active */

/* Function declarations
*/
char get_char();

/*GLOBAL***************************************************************

	ascode - MNP RS-232 interrupt service routine

     This routine is called by 'async' (in module 'async.asm')
     to service an async I/O interrupt.

**********************************************************************/

ascode()
{

char int_id_reg;			/* value in int id reg */

/* Handle interrupts for as long as there are any. 
*/
for (;;)
	{

/* Read the Interrupt Identification Register.  If bit 0 is on, 
** there is no interrupt.  In this case cancel the interrupt by writing ** the "end-of-interrupt" byte to the Oper Ctl Word 2 and return. 
*/
	if ((int_id_reg = inp(iir_add)) & BIT0_ON)
		{
		outp(INT_8259A_PORT, EOI);
		return;
		}

/* Since bit 0 is off, there is an interrupt.  Take action based
** on the type of interrupt. 
*/
	switch (int_id_reg)
		{

/* Receive buffer full interrupt
*/
		case RCV_DATA:				
			rcv_framer();
			break;

/* Transmit buffer empty interrupt
*/
		case THR_EMPTY:
			snd_framer();
			break;

/* Modem status change interrupt
*/
		case MODSTAT_CHNG:			
			mod_stat();
			break;
		}
	}
}

/*GLOBAL***************************************************************

	lne_stat - physical-connection status routine

**********************************************************************/

int lne_stat()
{

/* If the carrier detect bit is 1, return success (physical-connection
** still active).  Otherwise, return with a failure indication. 
*/
if (linestat & 0x80)
    return (SUCCESS);
else
    return (1);
}

/*GLOBAL***************************************************************

	trigger_sf - initiate send framer

**********************************************************************/

void trigger_sf()
{

extern int port_add;

/* If the RS-232 transmitter is not already busy, then a byte must 
** be placed in the transmit buffer to begin interrupt-driven sending.
** This is the start of a frame and thus the first byte to be sent is
** the SYN which begins the frame start flag.  Also the send framer's
** state variable is initialed here.
*/ 
if (!modem_out_busy)
	{
	modem_out_busy = 1;				
	outp(port_add, SYN);	
	sf_state = SF_DLE;
	}
}

/*LOCAL----------------------------------------------------------------

	get_char - get byte from send buffer

---------------------------------------------------------------------*/

char get_char()
{

/* Return the byte at the current send framer pointer position.
** In the process, advance the point for next time and reduce the
** number of bytes remaining by one.
*/
sf_len--;					
return (*sf_ptr++);
}

/*LOCAL----------------------------------------------------------------

	mod_stat - modem status change interrupt service routine

---------------------------------------------------------------------*/

void mod_stat()
{

/* Read modem status register and save the value.
*/
linestat = inp(iir_add + 4);

/* If carrier has been lost, signal the mainline that the link
** also has been lost by lowering the 'link established' flag. 
*/
if (!(linestat & 0x80))
    CLRBIT1(LINK_EST)	

}

/*LOCAL----------------------------------------------------------------

	put_char - store an LT data byte

---------------------------------------------------------------------*/

void put_char()
{

/* Ignore this byte if the amount of received data exceeds the
** negotiated maximum user data size for the LT LPDU.  This could occur
** if the frame is broken or if the correspondent is misbehaving. 
*/
if (rf_datlen >= lcb.max_data_sz)
	return;

/* Otherwise, accept this byte. 
*/
rf_datlen++;				
fcscalc (&rcv_fcs, rcv_char);

/* Now store the data byte in the receive buffer using the temporary
** insert pointer.  Advance the pointer, checking for wrap.
*/
*rf_tiptr++ = rcv_char;			
if (rf_tiptr >= rbuf + RBUF_LEN)
	rf_tiptr = rbuf;

}

/*LOCAL----------------------------------------------------------------

	put_hdr - store an LPDU header byte

---------------------------------------------------------------------*/

void put_hdr()
{

/* Store header byte in header buffer.  Increment count of bytes in
** the buffer and include the byte in the receive fcs.
*/
rf_hdrlen++;
*rf_hptr++ = rcv_char;			      
fcscalc (&rcv_fcs, rcv_char);

}

/*LOCAL----------------------------------------------------------------

	RCV_FRAMER - Receive Framer

---------------------------------------------------------------------*/

void rcv_framer()
{

extern int port_add;

/* Get received data byte and save it for later
*/
rcv_char = inp(port_add);

/* Take action based on current state of receive finite state machine.
** 'rf_state' is the state variable.
*/
switch (rf_state)
	{

/* State RF_INIT - This is the initial state.  Search the received
** data stream for a SYN character which may be the beginning
** of the start flag sequence of a byte mode frame. 
*/
	case RF_INIT:
		if (rcv_char == SYN)
			rf_state++;	
		break;

/* State RF_DLE - Search for the second byte in the start flag, a DLE
** byte.  Go back to the initial state (SYN search) if this byte is
** something other than a DLE.
*/
	case RF_DLE:
		if (rcv_char == DLE)
			 rf_state++; 
		else 
			rf_state--;		
		break;

/* State RF_STX - If the received byte is an STX then a frame start flag
** has been found.	In this case, initialize to receive an LPDU.
** Otherwise, go back to looking for a SYN. 
*/
	case RF_STX:
		if (rcv_char == STX)
			{
			get_b(&rlkb,&hdr_struct);
			rf_hptr = hdr_struct->bptr;
			hdrcnt=rf_hdrlen=rf_datlen=rdle_flg=0;
			rcv_fcs.hi=rcv_fcs.low=NULL;
	    		rf_state++; 		
	    		}
		else				
	    		rf_state = RF_INIT; 	    
		break;

/* State RF_HDLEN - Get the first byte of the LPDU, the length
** indicator.  Wait for the next byte if this happens to be a DLE (in a
** good frame the DLE will be doubled).  If the value  received is
** greater than this implementation's maximum header length, it must be
** a protocol error or the value is broken.  In either case, ignore this
** LPDU. This is like a broken frame, so force the sender to ack. 
*/
	case RF_HDLEN:
		if ((rcv_char != DLE) || ((rcv_char == DLE) && rdle_flg))
			{
			rdle_flg = 0;		
		
			if (rcv_char > MAX_HDRLEN)
				{
				ret_b(&rlkb, hdr_struct);	
				SETBIT2(FORCE_ACK)
				rf_state = RF_INIT;		
				}
	    		else
				{
				hdrcnt = rcv_char;
				put_hdr();
				rf_state++;
				}
	    		}
		else
	    		rdle_flg = 1;
		break;

/* State RF_HEADER - Receive header bytes, assuming the length indicator
** was correct.  Any bytes following the header will temporarily be
** assumed to be data.
*/
	case RF_HEADER:
		if ((rcv_char != DLE) || ((rcv_char == DLE) && rdle_flg)) 
			{
	    		rdle_flg = 0;
	    		put_hdr();
	    		if (!(--hdrcnt))
				rf_state++;
	    		}
		else
	    		rdle_flg = 1;
		break;

/* State RF_INFOL -  Receive first data byte.  Note that we must check
** for the stop flag just in case the length indicator byte was broken.  ** When the first data byte is received, the temporary insert pointer
** into the data receive buffer is initialized to the current location
** of the real pointer.
*/
	case RF_INFOL:
		if ((rcv_char != DLE) || ((rcv_char == DLE) && rdle_flg))
			{
	    		if ((rcv_char == ETX) && rdle_flg) 
				{
				fcscalc(&rcv_fcs, rcv_char);
				rf_state = RF_FCS1;
				}
	    		else
				{
				rdle_flg = 0;
                	rf_tiptr = rb_iptr; 		
	        		put_char();
				rf_state++; 		   
				}
			}
		else
	    		rdle_flg = 1;
		break;

/* State RF_INFO - Receive data bytes until the stop flag is found.
*/
	case RF_INFO:
		if ((rcv_char != DLE) || ((rcv_char == DLE) && rdle_flg))
			{
	    		if ((rcv_char == ETX) && rdle_flg)
				{
				fcscalc(&rcv_fcs, rcv_char);
				rf_state++;		
				}
	    		else
				put_char();
	    		rdle_flg = 0;
	    		}
		else
	    		rdle_flg = 1;
		break;

/* State RF_FCS1 - Receive first byte of fcs.
*/
	case RF_FCS1:
		rfcs.hi = rcv_char;
		rf_state++;
		break;

/* State RF_FCS2 - Receive second (low) byte of fcs and take end-of-
** frame action.  If the calculated fcs is not identical to that
** received, the received frame is "broken".  Signal this to the sender
** by setting the LPDU type to 0 and setting the FORCE_ACK flag.  If the
** FCS's match, take action based on the type of LPDU received. 
*/
	case RF_FCS2:
		if ((rcv_fcs.hi != rfcs.hi) || (rcv_fcs.low != rcv_char))
			{
	    		lcb.lpdu_type = 0;
	    		SETBIT2(FORCE_ACK);
	    		ret_b(&rlkb, hdr_struct);
	    		}
		else
			{
	    		switch (lcb.lpdu_type = *(hdr_struct->bptr + PDU_TYPE))
				{
				case LT:
		    			rcv_lt();
		    			break;

				case LA:
		    			rcv_la();
		    			break;

/* Link Request - If an LR arrives during what we think is the data
** phase, the other side may be retransmitting. In this case, just
** discard the LR.  Otherwise, save the LR for the mainline to process.
** In either case, signal the mainline to ack. 
*/
				case LR:
		    			if (BIT1SET(LINK_EST))
						ret_b(&rlkb, hdr_struct);
		    			else
						rlkb.used_lst = hdr_struct;  
		    			SETBIT2(FORCE_ACK)
		    			break;

				case LN:
		    			rcv_ln();
		    			break;

				case LNA:
		    			rcv_lna();
		    			break;

/* Link Disconnect - Signal link termination to the mainline.  If the LD
** contains a user reason byte (signalled by parm 1 having a value of
** 255), save it in the LCB. 
*/
				case LD:
		    			CLRBIT1(LINK_EST)
		    			if (*(hdr_struct->bptr + 4) == 255)
						{
						p_mnpcb->ld_source = 1;
						p_mnpcb->ld_reason = 
							*(hdr_struct->bptr+7);
						}
		    			else
						p_mnpcb->ld_source = 0;
		    			break;
				}
	    		}

/* Signal the mainline that a frame has been received and reset the
** receive frame state machine for the next frame. 
*/
	    		frame_rcvd = 1;			
	    		rf_state = RF_INIT; 	
	    		break;
		}

}

/*LOCAL----------------------------------------------------------------

	rcv_la - process a received LA LPDU

---------------------------------------------------------------------*/

void rcv_la()
{

unsigned char *p;
unsigned i;

/* Ignore this LA LPDU if a destructive attention is in progress. 
*/
if (BIT3SET(LN_SENT))
	{
	ret_b(&rlkb,hdr_struct);
    	return;
    	}

/* For reasons of robustness, check the validity of the LA LPDU 
*/
p = hdr_struct->bptr + 2;			/* point to parm 1 */
if ((*p++ != 1) || (*p != 1))
	{
	ret_b(&rlkb,hdr_struct);
	return;
	}
p += 2;
if ((*p++ != 2) || (*p != 1))
	{
	ret_b(&rlkb,hdr_struct);
	return;
	}

/* If the receive sequence number of this LA is the same as the last
** one received, it may be an implied negative acknowledgment.  Force
** the mainline to consider retransmission by setting the 'force
** retransmission' flag.  Otherwise, save the new number as the last
** acked and set the 'good ack received' flag. 
*/
p -= 2;
if (*p == lcb.ltssn_acked)
	SETBIT2(FORCE_RET)
else
	{
	lcb.ltssn_acked = *p;
    	SETBIT1(LA_RECEIVED)
    	}

/* Process the new credit information received. If it is zero, return.
*/
p += 3;
if ((lcb.rem_credit = *p) == 0)
	{
	ret_b(&rlkb,hdr_struct);
    	return;
    	}

/* If credit is not zero and this ack was a good one, take into account
** the LT LPDUs that may have been sent since the correspondent sent
** this LA.  That is, subtract from the credit the number of these not
** yet acked LTs. 
*/
if (BIT1SET(LA_RECEIVED) && tbcnt)
	{
	i = (lcb.ltssn_acked - *(ftb.used_lst->bptr + LT_SEQ)) + 1;
    	i = tbcnt - i;
    	if (lcb.rem_credit >= i)
		{
		lcb.rem_credit -= i;
        	if (lcb.rem_credit < 0)
			lcb.rem_credit = 0;
        	}
    	else
		lcb.rem_credit = 0;
    	}

/* Clean up 
*/
ret_b(&rlkb, hdr_struct);

}

/*LOCAL----------------------------------------------------------------

	rcv_ln - process a received LN LPDU

---------------------------------------------------------------------*/

void rcv_ln()
{

unsigned char *p;

/* Check the sequence number of this LN LPDU.  If it is the next one
** in the sequence space, signal its arrival to the sender by
** setting the LN_RECEIVED flag.  Also remember the type of the LN LPDU
** by saving the type parameter value in the LCB. 
*/
p = hdr_struct->bptr + 4;
if (*p == (unsigned char) ++lcb.ln_rsn)
	{
	lcb.ln_rtype = *(p + 3);
    	SETBIT3(LN_RECEIVED)
    	}

/* If this is not the next one, then decrement the receive sequence
** number which was incremented above and signal the need to send an
** LNA LPDU.
*/
else
	{
	lcb.ln_rsn--;
	SETBIT3(FORCE_LNA)
	}

ret_b(&rlkb, hdr_struct);
}

/*LOCAL----------------------------------------------------------------

	rcv_lna -  process a received LNA LPDU

---------------------------------------------------------------------*/

void rcv_lna()
{

unsigned char *p;

/* If the LNA LPDU is an acknowledgment of an outstanding LN, its
** sequence number will match that of the last LN sent.	In this
** case, signal the sender that the LN has been acknowledged.  
** Otherwise, just ignore this LPDU. 
*/
if (*(hdr_struct->bptr + 4) == lcb.ln_ssn)
	{
	lcb.ln_ret_cnt = NULL;
	CLRBIT3(LN_SENT)
	CLRBIT3(LN_TIMER)
	ln_tm=NULL;	
	}
ret_b(&rlkb, hdr_struct);
}

/*LOCAL----------------------------------------------------------------

	rcv_lt - process a received LT LPDU

---------------------------------------------------------------------*/

void rcv_lt()
{

/* Ignore this LT LPDU if a destructive attention is in progress. 
*/
if (BIT3SET(LN_SENT))
	{
	ret_b(&rlkb, hdr_struct);
    	return;
    	}

/* Reset window timer, if window timer is enabled. 
*/
if (BIT2SET(WNDW_TIMER))
	fcw_tm = lcb.window_tmr;

/* Accept this LT LPDU if it is the next one in the sequence space
** and there is room for it (i.e. there is a receive credit). If there
** is no room, the buffer pointer will point to a previous LT and the
** conditional test will fail. 
*/
if (*(hdr_struct->bptr + LT_SEQ) == (unsigned char) ++lcb.lt_rsn)
	{ 
	CLRBIT3(DUP_IGNORED)
    	rb_iptr = rf_tiptr;	
    	rb_cnt = rb_cnt + rf_datlen;
    	}

/* Otherwise, ignore the LT LPDU. In this case, set the FORCE_ACK flag
** to get the sender to ack as rapidly as possible.
*/
else
	{
	lcb.lt_rsn--;
    	SETBIT2(FORCE_ACK)

/* If the LT LPDU is the same as the last one which arrived, i.e. it is
** an immediate duplicate, and this is the first occurrence of the
** immediate duplicate, then don't force the sender to ack this time. 
*/
	if ((*(hdr_struct->bptr + LT_SEQ) == lcb.lt_rsn)
		&& !BIT3SET(DUP_IGNORED))
		{
		CLRBIT2(FORCE_ACK)
		SETBIT3(DUP_IGNORED)		
		}
    	}

/* Clean up 
*/
ret_b(&rlkb, hdr_struct);
}

/*LOCAL----------------------------------------------------------------

	snd_framer - handle a transmit holding register empty
		        interrupt.  This subroutine sends a byte-mode frame.

---------------------------------------------------------------------*/

void snd_framer()
{

extern int port_add;

/* Take action based on the current state of the framer state machine. 
*/
switch (sf_state)
	{

/* State SF_INIT - initial state.  If the mainline has designated an
** LPDU to frame, the sf_busy flag will be true.  If this is the case,
** begin the start flag of the frame. 
*/
    case SF_INIT:
	if (sf_busy)
		{
		snd_char = SYN;
		sf_state++;
		break;
		}
	else
		{
		frame_dne = 1;
		modem_out_busy = 0;
		return;
		}

/* State SF_DLE - Send DLE of start flag. 
*/
	case SF_DLE:
		snd_fcs.hi = snd_fcs.low = 0;
		snd_char = DLE;
		sf_state++;				
		break;

/* State SF_STX - Send STX of start flag. 
*/
	case SF_STX:
		snd_char = STX;
		sf_state++;
		break;

/* State SF_INFO -  As long as there are info field bytes, send a 
** byte and double DLE's as necessary.  At the end of the information
** field of the frame, start the end flag.   Signal the mainline code
** that it can enqueue another frame if it is already waiting on
** 'frame_snt'.  Clearing 'sf_busy' will cause the framer to stop after
** the end flag unless another frame has been enqueued. 
*/
	case SF_INFO:
		if (sf_len > 0)
			{
			if ((snd_char = get_char()) == DLE)
			sf_state++;
			fcscalc(&snd_fcs, snd_char);
			}
		else
			{
			sf_busy = sf_lt = 0;
			frame_snt = 1;
			snd_char = DLE;
			sf_state = SF_ETX;
			}
		break;

/* State SF_DLEINFO - Double an info field DLE.
*/
	case SF_DLEINFO:
		snd_char = DLE;
		sf_state--;
		break;

/* State SF_ETX - Send ETX of end flag.
*/
	case SF_ETX:
		snd_char = ETX;
		fcscalc(&snd_fcs, snd_char);
		sf_state++;
		break;

/* State SF_FCS1 - Send first byte of frame check sequence.
*/
	case SF_FCS1:
		snd_char = snd_fcs.hi;
		sf_state++;
		break;

/* State SF_FCS2 - Send second byte of frame check sequence.
** After this byte is sent, the send framer goes back to its
** initial state.
*/
	case SF_FCS2:
		snd_char = snd_fcs.low;
		sf_state = SF_INIT;
		break;
	}

/* Send byte out RS-232 port 
*/
outp(port_add, snd_char);

}
