/* IDSERIO.C   Interrupt-Driven Serial IO*/

#include <stdio.h>
#include <ctype.h>
#include <dos.h>
#include <bios.h>
#include <conio.h>

#define TRUE 1
#define FALSE 0
#define EOS '\0'

#define CONTROL(x)  (x-0x40)
#define ESC_KEY	    CONTROL('[')

#define COM_PARAMS  0xE3    /* 9600,N,8,1 */

#define RXQSIZE 512
#define TXQSIZE 512

#define P8259_0		0x20
#define P8259_1		0x21
#define END_OF_INT	0x20

#define XON_ASCII	(0x11)
#define XOFF_ASCII	(0x13)

#define BIOS_DATA	((int far *)(0x400000L))

#define IER (comport + 1)
#define IIR (comport + 2)
#define LCR (comport + 3)
#define MCR (comport + 4)
#define LSR (comport + 5)
#define MSR (comport + 6)

#define RDAINT	1
#define THREINT	2
#define RLSINT	4
#define MSINT	8

#define MCRALL	15
#define MCROFF	0

#define IERALL	(RDAINT+THREINT+RLSINT+MSINT)
#define IEROFF	0

#define THREOFF	0xfd

#define MDMSTATUS	0
#define TXREGEMPTY	2
#define RXDATAREADY	4
#define RLINESTATUS	6

#define XON_RCVD	1
#define XOFF_RCVD	0
#define XON_SENT	1
#define XOFF_SENT	0

#define HI_TRIGGER(x)	(3*x/4)
#define LO_TRIGGER(x)	(x/4)

#define bit0(i)		(i & 0x0001)

#define turnon_int(i,j) \
	if(((j=inp(IER))&i)==0)outp(IER,(j|i))

#define report_error(s)	fprintf(stderr,s)

typedef struct 	QTYPE
{
	int count;
	int front;
	int rear;
	int maxsize;
	char *data;
} QTYPE;

static char	rxbuf[RXQSIZE], txbuf[TXQSIZE];

static QTYPE 	rcvq = {0, -1, -1, RXQSIZE, rxbuf},
		trmq = {0, -1, -1, TXQSIZE, txbuf};

int  s_linestatus, s_modemstatus;

static  QTYPE *txq = &trmq, *rxq = &rcvq;
static short 	comport=0,
		enable_xonxoff = 1,
		rcvd_xonxoff = XON_RCVD,
		sent_xonxoff = XON_SENT,
		send_xon = FALSE,
		send_xoff = FALSE,
		int_number = 12,
		int_enable_mask = 0xef,
		int_disable_mask = 0x10;

int s_sendchar(int);
int s_rcvchar(void);
int s_setup(short, unsigned);
int s_cleanup(void);

char *q_getfrom( QTYPE *, char *);
int   q_puton( QTYPE *, char *);

void interrupt far s_inthndlr(void);

static void s_rda(void);
static void s_trmty(void);
static void (interrupt far *old_handler)();

/*-----------------------------------------------------*/
main(int argc, char **argv)
{
  int ch, port_number = 0;

  if(argc > 1) port_number = atoi(argv[1]) - 1;
   printf("\nSERIO -- Serial I/O at 9600,8,N,1 \
     using port COM%d\n", port_number+1);
       printf("\nConnecting ...\n");

       s_setup(port_number, COM_PARAMS);

       while (TRUE)
       {
	 if ((ch = s_rcvchar()) != -1) putch(ch);
	 if (kbhit() != 0)
	 {
	   ch = getch();
	   if (ch == ESC_KEY)
	   {
		s_cleanup();
		return;
	   }
	   else
		s_sendchar(ch);
	 }
       }
}

/*--------------------------------------------------------*/

void interrupt far s_inthndlr(void)
{
  int c;
  register int int_id, intmask;

  enable();

  while (TRUE)
  {
	int_id = inp(IIR);
	if (bit0(int_id) == 1)
	{
	  outp(P8259_0, END_OF_INT);
	  return;
	}
	if (int_id >= RXDATAREADY)
		turnon_int(THREINT,intmask);

	switch (int_id)
	{
	  case MDMSTATUS:
				s_modemstatus = inp(MSR);
				break;
	  case TXREGEMPTY:  	s_trmty();
				break;
	  case RXDATAREADY: 	s_rda();
				break;
	  case RLINESTATUS:
				s_linestatus = inp(LSR);
				break;
	}
  }
}

/*----------------------------------------------------------*/

static void s_rda(void)
{
	register int intmask;
	char c;

	c = inp(comport);
	if(enable_xonxoff) {
	   if(c == XON_ASCII) {
	      rcvd_xonxoff = XON_RCVD;

	      turnon_int(THREINT, intmask);
	      return;
	   }
	   if(c == XOFF_ASCII) {
	      rcvd_xonxoff = XOFF_RCVD;

	      intmask = inp(IER);
	      if (intmask & THREINT)
			outp(IER, intmask & THREOFF);
	      return;
	   }
	}
	q_puton(rxq, &c);

	if(enable_xonxoff){
	  if(rxq->count >= HI_TRIGGER(RXQSIZE)  &&
	     sent_xonxoff != XOFF_SENT ) {

	      send_xoff = TRUE;

	      turnon_int(THREINT, intmask);
	  }
	}
}

/*---------------------------------------------------*/

static void s_trmty(void)
{
  char c;
  register int ierval;

  if (send_xoff == TRUE) {
    outp(comport, XOFF_ASCII);
    send_xoff = FALSE;
    sent_xonxoff = XOFF_SENT;
    return;
  }
  if (send_xon == TRUE) {
     outp(comport, XON_ASCII);
     send_xon = FALSE;
     sent_xonxoff = XON_SENT;
     return;
  }

  if( q_getfrom(txq, &c) != NULL){
     outp(comport, c);
     return;
  }

  ierval = inp(IER);
  if (ierval &THREINT) outp(IER, ierval & THREOFF);
  }

/*--------------------------------------------------------*/

int s_setup(short port_number, unsigned commparams)
{
   int intmask;

   if (port_number < 0 || port_number > 1)
		report_error("Invalid port number!\n");

   comport = *(BIOS_DATA + port_number);
   if (comport == 0)
   {
     report_error("BIOS could not find port!\n");
     return(0);
   }

   if (port_number == 0)
   {
      int_enable_mask = 0xef;
      int_disable_mask = 0x10;
      int_number = 12;
   }
   if (port_number == 1)
   {
      int_enable_mask = 0xf7;
      int_disable_mask = 8;
      int_number = 11;
   }

   disable();
   setvect(int_number, s_inthndlr);
   enable();

   bioscom(0, commparams, port_number);

   rcvd_xonxoff = XON_RCVD;
   if (sent_xonxoff == XOFF_SENT)
      send_xon = TRUE;
   else
      send_xon = FALSE;
   send_xoff = FALSE;

   disable();

   outp(MCR, MCRALL);

   outp(IER, IERALL);

   intmask = inp(P8259_1) & int_enable_mask;
   outp(P8259_1, intmask);

   enable();

   return(1);
}

/*----------------------------------------------------------*/

int s_cleanup(void)
{
   int intmask;

   disable();

   outp(IER, IEROFF);

   outp(MCR, MCROFF);

   intmask = inp(P8259_1) | int_disable_mask;
   outp(P8259_1, intmask);

   setvect(int_number, old_handler);

   enable();
   return 0;
}

/*------------------------------------------------------------*/

int s_sendchar(int ch)
{
   int retval, intmask;

   disable();
   retval = q_puton(txq, (char *)&ch);
   enable();

   if (rcvd_xonxoff != XOFF_RCVD)
		turnon_int(THREINT, intmask);
   return(retval);
}

/*--------------------------------------------------------------*/

int s_rcvchar(void)
{
   int ch, intmask;

   if(enable_xonxoff)
   {
   if(rxq->count <= LO_TRIGGER(RXQSIZE) &&
	sent_xonxoff != XON_SENT )
	{
	   send_xon = TRUE;
	   turnon_int(THREINT, intmask);
	}
   }
   disable();
   if (q_getfrom(rxq, (char *)&ch) == NULL)
   {
      enable();
      return(-1);
   }
   else
   {
   enable();
      return(ch);
   }
}

/*----------------------------------------------------*/

char *q_getfrom( QTYPE *queue, char *data)
{
   char *current;
   current = NULL;
   if(queue->front == -1) return(current);

   current = &(queue->data[queue->front]);
   *data = *current;
   queue->count--;
   if(queue->count == 0)
   {

      queue->front = queue->rear = -1;
      return(current);
   }

   if (queue->front == queue->maxsize-1)
      queue->front = 0;
   else
      queue->front++;
   return(current);
}

/*--------------------------------------------------*/

int q_puton(QTYPE *queue, char *data)
{

   if(queue->count == queue->maxsize) return(0);

   if(queue->rear == queue->maxsize-1)
      queue->rear = 0;
   else
      queue->rear++;

   queue->data[queue->rear] = *data;
   queue->count++;
   if(queue->front == -1) queue->front = 0;
   return(1);
}







