/********************* Cut here for separate header file *********************/
/* rs232.c v1.6 */
/* check path ! */
#ifndef rs_232_h
#include "..\com\rs232.h"
#endif
/***

      RS232: Set of general purpose functions providing fully buffered
      interrupt driven serial I/0.  Supports baud rates from 110 to
      115.2K on serial ports 1 - 4.  Compiled and tested with Turbo C 2.0,
      Turbo C++, Borland C++ 2.0, Borland C++ 3.1.

      3/5/92
      v1.6
      revised 10/12/92, 11/2/92, 12/13/92, 2/7/93, 5/2/93, 7/30/94
              11/30/94, 12/2/94, 1/21/95
      copyrght. C. Karcher 1992,93,94,95
      Seattle WA
      CSID 76406,536

      11/2/92  Fixed rs_sndstr bug: sending string longer than buffer len.
               Fixed rs_timer bug: when timer was cleared and then read
               before it had a chance to increment past 0, 176 was returned.
               Fixed bug in interrupt service routine which prevented rs_sndstr
               from working with modem board which emulates 8250B UART.

      12/13/92 Corrected unterminated comment in rs_initport where defining
               rs_ss.fcr.  Changed timer rollover constant type from L to UL
               in rs_timer.

      1/9/93   Fixed bug in rs_initport where defining base address for ports
               3 and 4 - IRQ number and interrupt mask was left unitialized
               if BIOS data area showed no base address for port.

      2/7/93   Corrected bug in interrupt service routine which caused
               receive FIFO to be read incorrectly.  Corrected bug in
               rs_sndsr and rs_sndbyt preventing an xmit interrupt from
               being generated.  Added preprocessor directives and code
               to use polling method instead of interrupts for character
               transmission if RS_POLLED_XMIT is defined.

      5/2/93   Added preprocessor directives in interrupt service routine
               to prevent code generated by TC2.0 and earlier from trashing
               incoming characters in large memory models.

      7/30/94  Corrected preprocessor directives in interrupt service routine
               to prevent transmit interrupts from being enabled when hardware
               flow control is being used in conjunction with polled transmits.
               Removed preprocessor directives in interrupt routine which
               caused received byte to be transferred from _AL to _CL only
               when compiled with TC 2.0 and earlier.  Directives did not
               work properly with some European Borland compilers.  Added
               rs_scanin function to scan input buffer for selected string.

     11/30/94  Corrected bug in rs_setflow which prevented hardware flow
               control from working properly.

     12/02/94  Added support for user defined base address and IRQ for
               serial adaptor.  Corrected problem in rs_initport which
               sometimes prevented function from detecting UART with slower
               8250x boards.  Added pre-processor directives for C++
               compilation.

     12/17/94  Corrected problem with user defined IRQ above 7.  ISR was
               sending EOI to cascade PIC.

       1/4/94  Added non-destructive input function (rs_peek).  Corrected
               rs_scanin function to return offset to beginning of search
               string instead od end.

***/

#include<dos.h>        /* need dos.h for port i/o and interrupt funcs. */
#include<stdarg.h>     /* for variable arguments in some functions */

#ifndef NULL
  #if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
    #define NULL    0
  #else
    #define NULL    0L
  #endif
#endif

unsigned rs_timer(int rs_cmd);
void rs_close(void);

/*****************************************************************/
/***          global variables - all names begin with rs_      ***/
/*****************************************************************/
struct rs_statics{                     /* static variables */
  int int_no;
  int flow;
  int xmitfifo;
  int thr;
  int rbr;
  int iir;
  int fcr;
  int ier;
  int mcr;
  int lsr;
  int lcr;
  int msr;
  int uart;
  int pic;
  unsigned ibuf_siz;
  unsigned obuf_siz;
  char xon;
  char xoff;
  char hdw_flw;
  unsigned char *in_buf;
  unsigned char *out_buf;
  unsigned char oldmask;
  }rs_ss;

volatile struct rs_dynamics{           /* dynamic variables */
  unsigned in_head;
  unsigned in_tail;
  unsigned out_head;
  unsigned out_tail;
  unsigned rcv_cnt;
  unsigned char ier_msk;
  unsigned char msr_cod;
  unsigned char err_cod;
  }rs_ds;

int rs_portopen = 0;
struct rs_userport rs_user = {0,'\0'};
void interrupt (*rs_oldvec)(void) = NULL;

/* Interrupt service routine - Get received character from port rs_ss.rbr,
   store in rs_ss.in_buf and increment rs_inrcv.  If flow control enabled,
   update global variable rs_ss.iermsk to enable or disable output as
   required */
void interrupt rs_inthndlr(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern void interrupt (*rs_oldvec)(void);
  int rs_xmitcnt;

#ifndef RS_POLLED_XMIT
  if(rs_ss.uart)
    outportb(rs_ss.ier,rs_ds.ier_msk);   /* nudge xmit intrpt if not 8250B */
#endif
  _AL = (inportb(rs_ss.iir) & '\x07');   /* get interrupt id */
  if(!(_AL & '\x01')){     /* interrupt pending ? */
    do{
      if(_AL == '\x06'){   /* receive error ? */
        rs_ds.err_cod = inportb(rs_ss.lsr); /* record error */
        }
      else if((_AL & '\x04')){  /* data received interrupt ? */
        do{
          _AL = inportb(rs_ss.rbr); /* get the byte */
          _CL = _AL;
          if(rs_ss.flow == 2){
            if(_CL == rs_ss.xon){
#ifndef RS_POLLED_XMIT
              outportb(rs_ss.ier,'\x0F');
#endif
              rs_ds.ier_msk = '\x0F';
              }
            else if(_CL == rs_ss.xoff){
#ifndef RS_POLLED_XMIT
              outportb(rs_ss.ier,'\x0D');
#endif
              rs_ds.ier_msk = '\x0D';
              }
            else
              goto rs_J1;
            }
          else{
            /* store incoming byte */
          rs_J1:
            *(rs_ss.in_buf + rs_ds.in_head++) = _CL;
            /* in case head ptr wrapped around */
            rs_ds.in_head &= rs_ss.ibuf_siz;
            rs_ds.rcv_cnt++; /* track rcv buffer overflow */
            }
          }while(inportb(rs_ss.lsr) & '\x01'); /* loop while data avail. */
        }
#ifndef RS_POLLED_XMIT
      else if(_AL & '\x02'){  /* transmit holding reg. empty ? */
        for(rs_xmitcnt = 0;rs_xmitcnt < rs_ss.xmitfifo;rs_xmitcnt++){
          /* send a byte if any to send */
          if(rs_ds.out_tail != rs_ds.out_head){
            outportb(rs_ss.thr,*(rs_ss.out_buf + rs_ds.out_tail++));
            /* in case tail pointer wrapped around */
            rs_ds.out_tail &= rs_ss.obuf_siz;
            }
          else
            break;
          }
        }
#endif
      else{ /* change in one of the modem control lines */
        rs_ds.msr_cod = inportb(rs_ss.msr); /* record modem status */
        if(rs_ss.flow == 1){
          if(rs_ds.msr_cod & rs_ss.hdw_flw)
            rs_ds.ier_msk = '\x0F'; /* enable xmit interrupts */
          else
            rs_ds.ier_msk = '\x0D'; /* disable xmit interrupts */
#ifndef RS_POLLED_XMIT
          outportb(rs_ss.ier,rs_ds.ier_msk);
#endif
          }
        }
      }while(!((_AL = inportb(rs_ss.iir)) & '\x01')); /* loop if int. pending */
    }
  else
    rs_oldvec(); /* no interrupt pending on entry - call old isr */


  enable();  /* enable interrupts */
  outportb(rs_ss.pic,'\x20'); /* non-specific EOI to 8259 */
  if(rs_ss.pic == 0xA0)
    outportb(0x20,'\x20');

}

/* rs_initport: Initialize port, interrupt vector and interrupt mask (see
   description with function prototype for details on arguments). */
int rs_initport(char rs_port,long rs_baud,char rs_parity,char rs_bits,
                char rs_stop,unsigned rs_userinbufsiz, char *rs_userinbuf,
                unsigned rs_useroutbufsiz,char *rs_useroutbuf)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  extern void interrupt (*rs_oldvec)(void);
  int rs_init[] = {0x6328,0x2029,0x2E43,0x4B20,0x7261,0x6863,0x7265,
                   0x3120,0x3939,0x2033,0x3439};
  int rs_dll,rs_dlm,rs_portbase,rs_ret;
  int far *rs_bda;
  unsigned char rs_dvsrl,rs_dvsrh,rs_mask,rs_irq,rs_tmp1,rs_tmp2,rs_tmp3;


  if(rs_portopen) /* if there's already a port open, close it */
    rs_close();
  rs_ss.oldmask = '\0';

  /* make sure buffer size is valid */
  if((rs_userinbufsiz - 1) & rs_userinbufsiz)
    return -1;
  rs_ss.ibuf_siz = rs_userinbufsiz - 1;
  if((rs_ss.in_buf = (unsigned char *)rs_userinbuf) == NULL)
    return -1;
#ifndef RS_POLLED_XMIT
  if((rs_useroutbufsiz - 1) & rs_useroutbufsiz)
    return -2;
  rs_ss.obuf_siz = rs_useroutbufsiz - 1;
  if((rs_ss.out_buf = (unsigned char *)rs_useroutbuf) == NULL)
    return -2;
#endif

  /* initialize buffer head and tail pointers */
  rs_ds.in_head = rs_ds.in_tail = rs_ds.out_head = rs_ds.out_tail = 0;

  if(rs_init[1])
    rs_bda = (int far*)MK_FP(0x40,0); /* get far pointer to BIOS data area */

  switch(rs_port){ /* find i/o port address and irq */
    case '1':
      rs_portbase = *rs_bda;
      if(rs_portbase == 0)
        rs_portbase = 0x3F8;
      rs_irq = 4;
      break;
    case '2':
      rs_portbase = *(rs_bda + 1);
      if(rs_portbase == 0)
        rs_portbase = 0x2F8;
      rs_irq = 3;
      break;
    case '3':
      rs_portbase = *(rs_bda + 2);
      if(rs_portbase == 0)
        rs_portbase = 0x3E8;
      if(rs_portbase == 0x3220) /* if it's a PS/2 */
        rs_irq = 3;
      else
        rs_irq = 4;
      break;
    case '4':
      rs_portbase = *(rs_bda + 3);
      if(rs_portbase == 0)
        rs_portbase = 0x2E8;
      if(rs_portbase == 0x3228) /* if it's a PS/2 */
        rs_irq = 4;
      else
        rs_irq = 3;
      break;
    case '0': /* user defined IRQ and base address defined by struct rs_user */
      if(! rs_user.base) /* make sure it's been init'd */
        return -3;
      rs_portbase = rs_user.base;
      rs_irq = rs_user.irq;
      break;
    default:
      return -3;
    }

  if(rs_irq <= 7){ /* find interrupt number and PIC address */
    rs_ss.int_no = rs_irq + 8;
    rs_ss.pic = 0x20;
    }
  else if(rs_irq <= 15){
    rs_irq -= 8;
    rs_ss.int_no = rs_irq + 0x70;
    rs_ss.pic = 0xA0;
    }
  else
    return -3;
  rs_mask = ('\x01' << rs_irq) ^ '\xFF';

  switch(rs_parity){
    case 'N':
      rs_parity = 0;
      break;
    case 'E':
      rs_parity = '\x18';
      break;
    case 'O':
      rs_parity = '\x08';
      break;
    case 'S':
      rs_parity = '\x38';
      break;
    case 'M':
      rs_parity = '\x28';
      break;
    default:
      return -4;
    }

  if(rs_bits == '7')
    rs_bits = 2;
  else if(rs_bits == '8')
    rs_bits = 3;
  else
    return -5;

  if(rs_stop == '1')
    rs_stop = 0;
  else if(rs_stop == '2')
    rs_stop = 4;
  else
    return -6;

  /* 8250 (or 16x50) registers: */
  /* out, bit 7 of LCR = 0, (Transmit Holding Register) */
  rs_ss.thr = rs_portbase + 0;
  /* in, bit 7 of LCR = 0, (Receive Buffer Register) */
  rs_ss.rbr = rs_portbase + 0;
  /* out, bit 7 of LCR = 1, (Divisor Latch LSB) */
  rs_dll = rs_portbase + 0;
  /* out, bit 7 of LCR = 1, (Divisor Latch MSB) */
  rs_dlm = rs_portbase + 1;
  /* out, bit 7 of LCR = 0, (Interrupt Enable Register)
                        bit 0 = 1 data rcvd
                        bit 1 = 1 transmit holding reg. empty
                        bit 2 = 1 data reception error
                        bit 3 = 1 change in modem status
                        bit 4-7 unused */
  rs_ss.ier = rs_portbase + 1;
  /* in, (Interrupt ID register)
                        bit  0  :  0 = interrupt pending
                        bits 2-1: 00 = modem status change - read status
                                  01 = transmit ready - output character or
                                       read iir to clear
                                  10 = data rcvd - read data
                                  11 = break or error
                        bits 7-6: 11 = 16550 in FIFO mode */
  rs_ss.iir = rs_portbase + 2;
  /* out, (FIFO control register - 16550)
                        bit 0: 1 = FIFO enable
                        bit 1: 1 = RCVR FIFO reset
                        bit 2: 1 = XMIT FIFO reset
                        bit 6 & 7: RCVR trigger level
                        (00 = 1, 01 = 4, 10 = 8, 11 = 14) */
  rs_ss.fcr = rs_portbase + 2;
  /* out, (Line Control Register)
                        bits 0-1: Character Length
                                     00 = 5 bits
                                     01 = 6 bits
                                     10 = 7 bits
                                     11 = 8 bits
                        bit 2: Number of stop bits
                                      0 = 1 (1.5 if character length 5)
                                      1 = 2
                        bit 3: Parity
                                      0 = no parity
                                      1 = parity generated
                        bit 4: Parity type
                                      0 = odd
                                      1 = even
                        bit 5: Stick Parity
                                      0 = disabled
                                      1 = always 1 if bit 3 = 1 & bit 4 = 0 or
                                          always 0 if bit 3 = 1 & bit 4 = 1 or
                                          no parity if bit 3 = 0
                        bit 6: Set Break
                                      0 = disabled
                                      1 = output string of 0s
                        bit 7: Enable write to baud divisor regs. if 1 */
  rs_ss.lcr = rs_portbase + 3;
  /* out, (Modem Control Register)
                        bit 0:        1 = data terminal ready
                        bit 1:        1 = request to send
                        bit 2:        1 = aux. output 1
                        bit 3:        1 = aux. output 2 - enables hdwr intrrpts.
                        bit 4:        1 = UART loopback mode
                        bit 5-7:      always 0 */
  rs_ss.mcr = rs_portbase + 4;
  /* in, (Line Status Register)
                        bit 0:        1 = character received
                        bit 1:        1 = rcvd data overrun
                        bit 2:        1 = parity error
                        bit 3:        1 = framing error
                        bit 4:        1 = break detected
                        bit 5:        1 = transmit holding reg. empty
                        bit 6:        1 = transmitter empty
                        bit 7:        1 = rcvr FIFO error (16550) */
  rs_ss.lsr = rs_portbase + 5;
  /* in, (Modem Status Register)
                        bit 0:        1 = delta clear to send
                        bit 1:        1 = delta data set ready
                        bit 2:        1 = delta ring indicator
                        bit 3:        1 = delta data carrier detect
                        bit 4:        1 = clear to send
                        bit 5:        1 = data set ready
                        bit 6:        1 = ring indicator
                        bit 7:        1 = data carrier detect */
  rs_ss.msr = rs_portbase + 6;

  /* check for existence of UART */
  outportb(rs_ss.fcr,'\0');         /* if 16550, make it a 16450 */
  rs_tmp1 = inportb(rs_ss.mcr);     /* save mcr */
  rs_tmp2 = inportb(rs_ss.ier);     /* save ier */
  outportb(rs_ss.ier,'\0');         /* disable interrupts at UART */
  inportb(rs_ss.rbr);               /* read rcv buffer register */
  inportb(rs_ss.lsr);               /* read lsr */
  inportb(rs_ss.msr);               /* read msr */
  inportb(rs_ss.iir);               /* read iir */
  if(inportb(rs_ss.iir) != '\x01')  /* read it again, should indicate... */
    return 0;                       /* ...no interrupt pending */
  outportb(rs_ss.lcr,'\0');         /* make sure out2 is 0 */
  outportb(rs_ss.ier,'\x02');       /* enable thre interrupt indication */
  outportb(rs_ss.ier,'\x02');       /* do it again for 8250 */
  goto rs_J1;                       /* give UART some time */
  rs_J1:
  rs_tmp3 = inportb(rs_ss.iir);     /* get iir */
  outportb(rs_ss.ier,rs_tmp2);      /* restore original values */
  outportb(rs_ss.mcr,rs_tmp1);
  if(rs_tmp3 != '\x02')             /* should have seen thre interrupt... */
    return 0;                       /* ...else port is unavailable */

  /* find out what kind of UART it is */
  rs_ss.xmitfifo = 1; /* initialize for no FIFO */
  outportb(rs_portbase + 7,'\x55');  /* try writing scratch register */
  if(inportb(rs_portbase + 7) != '\x55')  /* and then reading it */
    rs_ret = 1; /* if value written wasn't read it's an 8250B */
  else{
    /* check for presence of 16550, if 16550A,C,CF, enable FIFOs */
    outportb(rs_ss.fcr,'\x41'); /* enable FIFO's with 4 byte RCVR trig. level */
    rs_tmp1 = inportb(rs_ss.iir);
    rs_tmp1 &= '\xC0';
    if(rs_tmp1 == (unsigned char)'\xC0'){ /* 16550A,C,CF bits 6 & 7 are set */
      rs_ret = 4;
      rs_ss.xmitfifo = 16;   /* #of bytes to write to XMIT FIFO per interrupt */
      }
    else{                 /* otherwise, it's an 8250A, 16450 or 16550 */
      outportb(rs_ss.fcr,'\x0');
      if(rs_tmp1 == (unsigned char)'\x80') /* 16550 */
        rs_ret = 3;
      else                                 /* 8250A or 16450 */
        rs_ret = 2;
      }
    }

  /* get the baud rate divisor values */
  rs_dvsrh = 0;
  switch(rs_baud){
    case 110L:
      rs_dvsrh = '\x04';
      rs_dvsrl = '\x17';
      break;
    case 300L:
      rs_dvsrh = '\x01';
      rs_dvsrl = '\x80';
      break;
    case 600L:
      rs_dvsrl = '\xC0';
      break;
    case 1200L:
      rs_dvsrl = '\x60';
      break;
    case 2400L:
      rs_dvsrl = '\x30';
      break;
    case 4800L:
      rs_dvsrl = '\x18';
      break;
    case 9600L:
      rs_dvsrl = '\x0C';
      break;
    case 19200L:
      rs_dvsrl = '\x06';
      break;
    case 38400L:
      rs_dvsrl = '\x03';
      break;
    case 57600L:
      rs_dvsrl = '\x02';
      break;
    case 115200L:
      rs_dvsrl = '\x01';
      break;
    default:
      return -7;
    }

  rs_oldvec = getvect(rs_ss.int_no); /* get the old interrupt vector */
  setvect(rs_ss.int_no,rs_inthndlr); /* plug in the new one */

  outportb(rs_ss.ier,0);      /* disable UART interrupts */
  outportb(rs_ss.lcr,'\x80'); /* enable baud rate divisor registers */
  outportb(rs_dll,rs_dvsrl);  /* write divisor lo byte */
  outportb(rs_dlm,rs_dvsrh);  /* write divisor hi byte */
  outportb(rs_ss.lcr,(rs_parity | rs_bits | rs_stop)); /* characteristics */
  /* enable interrupts at UART, do not change modem control lines */

  outportb(rs_ss.mcr,(inportb(rs_ss.mcr) | '\x08')); /* set out2 */
  inportb(rs_ss.iir); /* clear out any data...*/
  inportb(rs_ss.lsr); /*...left in...*/
  inportb(rs_ss.rbr); /*...UART's status...*/
  inportb(rs_ss.msr); /*...registers */

  disable();
  outportb(rs_ss.ier,'\x0D'); /* enable UART interrupts */
  inportb(rs_ss.iir);
  rs_ss.oldmask = inportb(rs_ss.pic + 1); /* save old interrupt mask */
  rs_mask &= rs_ss.oldmask;

  outportb(rs_ss.pic + 1,rs_mask); /* interrupt now enabled */
  rs_ds.msr_cod = inportb(rs_ss.msr) & '\xF0'; /* initialize modem status */
  enable();

  rs_timer(0);             /* zero out tick counter */
  rs_ss.flow = 0;          /* initialize flow control */
  rs_ds.rcv_cnt = 0;       /* initialize receive count */
  rs_ds.err_cod = '\x00';  /* initialize error flags */
  rs_ds.ier_msk = '\x0F'; /* insure that xmit interrupts stay enabled */
  rs_portopen = 1;         /* set port open flag */
  rs_ss.uart = rs_ret - 1; /* set UART type */
  return rs_ret;

}

/* rs_close: Restore original 8259 interrupt controller mask value,
   disable UART interrupts and restore original interrupt vector. */
void rs_close(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  extern void interrupt (*rs_oldvec)(void);
  unsigned rs_time,rs_tail;

  if(! rs_portopen) /* no port to close */
    return;

  /* if buffer xmit in progress, wait til it's done */
  if(rs_ds.ier_msk == '\x0F'){
    rs_time = rs_timer(1);
    rs_tail = rs_ds.out_tail;
    while(rs_ds.out_head != rs_ds.out_tail)
      /* make sure output is moving else time out (wait one char period) */
      if(rs_timer(1) - rs_time > 4 && rs_tail == rs_ds.out_tail)
        break;
    }

  /* insure that tranmitter is empty before continuing */
  rs_time = rs_timer(1);
  while(! (inportb(rs_ss.lsr) & '\x40'))
    if(rs_timer(1) - rs_time > 4)
      break;

  if(rs_ss.oldmask){
    disable();
    outportb(0x20,'\xC7');           /* restore interrupt priorities */
    enable();
    }

  /* disable UART interrupts */
  outportb(rs_ss.ier,0);
  outportb(rs_ss.mcr,inportb(rs_ss.mcr) & '\xF7');

  /* restore old interrupt vector */
  if(rs_oldvec != NULL)
    setvect(rs_ss.int_no,rs_oldvec);
  rs_oldvec = NULL;

  rs_portopen = 0; /* signal port closed */

}

/* rs_sndbyt: Output byte via output buffer.  If no space in output buffer,
   wait til there is unless output is disabled via flow control, in which
   case return -1.  If RS_POLLED_XMIT is defined, the byte is written to
   the port when the transmit holding register becomes empy.  THRE interrupt
   and output buffer are not used.  Return -1 if output disabled via flow
   control. */

int rs_sndbyt(int rs_snd)
{

  extern int rs_portopen;
  extern volatile struct rs_dynamics rs_ds;
  extern struct rs_statics rs_ss;


  if(! rs_portopen) /* is a port open ? */
    return -1;

#ifndef RS_POLLED_XMIT /* interrupt driven output */
  while(((rs_ds.out_head + 1)  & rs_ss.obuf_siz) == rs_ds.out_tail){
    /* make sure there's room in the buffer */
    /* if xmit disabled via flow control, don't wait */
    if(rs_ds.ier_msk == '\x0D')
      return -1;
    }
  disable();
  *(rs_ss.out_buf + rs_ds.out_head++) = (unsigned char)rs_snd;
  rs_ds.out_head &= rs_ss.obuf_siz;
  enable();
  if(rs_ds.ier_msk != '\x0D'){
    outportb(rs_ss.ier,'\x0D');
    if(! rs_ss.uart)           /* if it's an 8250 or 8250B... */
      while(! (inportb(rs_ss.lsr) & '\x20')) /* ...wait for THRE */
        ;
    outportb(rs_ss.ier,'\x0F'); /* generate an interrupt if needed */
    }
#else /* polled mode output */
  if(rs_ds.ier_msk == '\x0D')
    return -1;
  while(! (inportb(rs_ss.lsr) & '\x20')) /* wait for THRE */
    ;
  outportb(rs_ss.thr,(unsigned char)rs_snd);
#endif
  return 0;

}


/* rs_sndstr: Output rs_sndcnt chars from rs_str to output buffer.  If not
   enough space in output buffer, wait til there is.  If output disabled
   via flow control, return count of characters written to output buffer
   if less than string length.  If rs_sndcnt is 0, output characters from
   rs_str until nul character is reached.  If RS_POLLED_XMIT is defined,
   string is written to port by polling the line status register for THRE
   and the buffer is not used.  If output is disabled via flow control,
   return count of characters sent */

int rs_sndstr(int rs_sndcnt, char *rs_str)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  int rs_x,rs_y;

  if(! rs_portopen) /* is a port open? */
    return -1;

  if(rs_sndcnt) /* string length specified? */
    rs_y = rs_sndcnt;
  else
    rs_y = 0x7FFF; /* max buffer length (`11/2) */

#ifndef RS_POLLED_XMIT /* interrupt driven transmit */
  for(rs_x = 0;rs_x < rs_y;rs_x++){
    /* if no string length specified, stop when nul is encountered */
    if(! rs_sndcnt){
      if(*(rs_str + rs_x) == '\0')
        break;
      }
    while(((rs_ds.out_head + 1)  & rs_ss.obuf_siz) == rs_ds.out_tail){
      /* if xmit disabled via flow control,don't wait */
      if(rs_ds.ier_msk == '\x0D')
        return rs_x;
      else{ /* otherwise, may need to get xmit interrupts going (`11/2) */
        outportb(rs_ss.ier,'\x0D');
        if(! rs_ss.uart)           /* if it's an 8250 or 8250B... */
          while(! (inportb(rs_ss.lsr) & '\x20'))  /* wait for THRE */
            ;
        outportb(rs_ss.ier,'\x0F'); /* kick transmit interrupt */
        }
      }
    disable();
    *(rs_ss.out_buf + rs_ds.out_head++) = (unsigned char)*(rs_str + rs_x);
    rs_ds.out_head &= rs_ss.obuf_siz;
    enable();
    }
  if(rs_ds.ier_msk != '\x0D'){
    outportb(rs_ss.ier,'\x0D');
    if(! rs_ss.uart)
      while(! (inportb(rs_ss.lsr) & '\x20'))
        ;
    outportb(rs_ss.ier,'\x0F'); /* generate an interrupt if needed */
    }
#else /* polled transmit */
  for(rs_x = 0;rs_x < rs_y;rs_x++){
    /* if xmit disabled via flow control,don't wait */
    if(rs_ds.ier_msk == '\x0D')
      return rs_x;
    /* if no string length specified, stop when nul is encountered */
    if(! rs_sndcnt){
      if(*(rs_str + rs_x) == '\0')
        break;
      }
    while(! (inportb(rs_ss.lsr) & '\x20'))
      ;
    outportb(rs_ss.thr,(unsigned char)*(rs_str + rs_x));
    }
#endif
  return 0;

}


/* rs_outfre: Return amount of free space available in output buffer.
   This function does not return a meaningful value if RS_POLLED_XMIT is
   defined because transmission does not use the buffer */
unsigned rs_outfre(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;

  return(rs_ss.obuf_siz + 1 - ((rs_ds.out_head - rs_ds.out_tail) &
                                                       rs_ss.obuf_siz));

}

/* rs_scanin: Scan for first occurance of string rs_scan_str in input buffer.
   Return -1 if not found, offset into buffer if found */
int rs_scanin(char *rs_scan_str)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  int rs_off,rs_incnt,rs_flg = 0,rs_len = 0;

  while(*(rs_scan_str + rs_len))
    rs_len++;

  if((! rs_portopen) || (! rs_ds.rcv_cnt) || (! rs_len))
    return -1;

  for(rs_off = 0;rs_off < rs_ds.rcv_cnt;rs_off++){
    if(*(rs_ss.in_buf + ((rs_ds.in_tail + rs_off) & rs_ss.ibuf_siz)) ==
         *(rs_scan_str + rs_flg)){
      rs_flg++;
      if(rs_flg == rs_len)
        return((rs_off - (rs_len - 1)) & rs_ss.ibuf_siz);
      }
    else
      rs_flg = 0;
    }

  return -1;

}

/* rs_peek: Return next available character from input buffer, leave
   character in buffer. Return -1 if none are available */
int rs_peek(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;

  if(! rs_portopen) /* is a port open? */
    return -1;

  if(! rs_ds.rcv_cnt) /* char available ? */
    return -1;
  return (int)(*(rs_ss.in_buf + rs_ds.in_tail));

}

/* rs_getbyt: Return next available character from input buffer - return
   -1 if none are available */
int rs_getbyt(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  unsigned char rs_byt;

  if(! rs_portopen) /* is a port open? */
    return -1;

  if(! rs_ds.rcv_cnt) /* char available ? */
    return -1;
  rs_byt = *(rs_ss.in_buf + rs_ds.in_tail++);
  rs_ds.rcv_cnt--;
  rs_ds.in_tail &= rs_ss.ibuf_siz;
  return (int)rs_byt;

}

/* rs_getstr: Get specified number of bytes from port buffer and place in
   user specified buffer.  Null terminate string.  If receive buffer
   has fewer than the requested number of characters available, copy all
   that are available.  Return number of bytes copied.  If rs_getcnt is 0,
   copy characters until a nul is encountered (include it in getbuf) */
int rs_getstr(int rs_getcnt,char *rs_getbuf)
{

  extern struct rs_statics rs_ss;
  extern int rs_portopen;
  int rs_x = 0;
  int rs_c,rs_y;

  if(! rs_portopen) /* is a port open? */
    return -1;

  if(rs_getcnt) /* was number of characters specified? */
    rs_y = rs_getcnt;
  else
    rs_y = rs_ss.ibuf_siz;

  while(rs_x < rs_y){
    if((rs_c = rs_getbyt()) < 0)
      break;
    else
      *(rs_getbuf + rs_x++) = (char)rs_c;
    if(! rs_getcnt){ /* if no length specified, end when nul is encountered */
      if(! rs_c)
        break;
      }
    }

  if(rs_getcnt) /* nul terminate if need be */
    *(rs_getbuf + rs_x) = '\0';
  return rs_x;

}

/* rs_inrcvd: Return number of received bytes waiting to be read from input
   buffer. */
unsigned rs_inrcvd(void)
{

  extern volatile struct rs_dynamics rs_ds;
  extern struct rs_statics rs_ss;

  if(rs_ds.rcv_cnt > rs_ss.ibuf_siz + 1){
    rs_ds.rcv_cnt = rs_ss.ibuf_siz + 2;
    return(rs_ss.ibuf_siz + 1);
    }
  else
    return rs_ds.rcv_cnt;

}

/* rs_error: Return last error detected as follows:
        bit 0: 1 = Buffer overrun
        bit 1: 1 = Receive data overrun
        bit 2: 1 = Parity error
        bit 3: 1 = Framing error
        bit 4: 1 = Break detected
        bit 7: 1 = Error in receive FIFO */
int rs_error(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  int rs_errtmp;

  rs_errtmp = (int)(rs_ds.err_cod & 0x9E);
  if(rs_ds.rcv_cnt > (rs_ss.ibuf_siz + 1)){ /* receive buffer overflow ? */
    rs_errtmp |= 0x01;                      /*   set flag */
    rs_ds.rcv_cnt = rs_ss.ibuf_siz + 1;
    }
  rs_ds.err_cod = '\0';
  return rs_errtmp;

}

/* rs_modctrl: If rs_cmd = 0, return value of modem status register as of
   most recent change.  If rs_cmd = 1, switch line determined by parameter1
   on or off depending on whether parameter2 is 1 or 0.  If rs_cmd = 2,
   return contents of modem control register */
int rs_modctrl(int rs_cmd,...)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  unsigned char rs_cntrl;
  int rs_tmp = 0;
  va_list rs_ap;

  va_start(rs_ap,rs_cmd);

  if(rs_portopen){
    if(rs_cmd == 2)
      rs_tmp = inportb(rs_ss.mcr);
    else if(rs_cmd == 1){ /*manipulate control line (modem control reg.) */
      rs_cntrl = (unsigned char)(va_arg(rs_ap,int));
      if(va_arg(rs_ap,int))
        rs_cntrl |= inportb(rs_ss.mcr);
      else{
        rs_cntrl ^= '\xFF';
        rs_cntrl &= inportb(rs_ss.mcr);
        }
      outportb(rs_ss.mcr,rs_cntrl);
      }
    else{ /* get modem status register */
      rs_tmp = (int)rs_ds.msr_cod;
      rs_ds.msr_cod &= '\xF0';
      }
    }
  else /* no port open */
    rs_tmp = -1;

  va_end(rs_ap);

  return rs_tmp;

}

/* send break */
int rs_break(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  unsigned char rs_tmp;
  unsigned rs_time;

  if(! rs_portopen) /* is a port open? */
    return -1;

  rs_tmp = (char)inportb(rs_ss.lcr);

  while(rs_ds.out_tail != rs_ds.out_head)
    if(rs_ds.ier_msk == '\x0D')
      break;

  while(!((inportb(rs_ss.lsr)) & '\x20'))
    ;

  rs_tmp = inportb(rs_ss.lcr);
  rs_time = rs_timer(1);
  outportb(rs_ss.lcr,rs_tmp | '\x40');
  while(rs_timer(1) - rs_time < 4)
    ;
  outportb(rs_ss.lcr,rs_tmp);

  return 0;

}

/* clear input buffer */
void rs_clrin(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;

  if(! rs_portopen) /* is a port open? */
    return;

  disable();
  rs_ds.in_tail = rs_ds.in_head;
  rs_ds.rcv_cnt = 0;
  if(rs_ss.xmitfifo == 16) /* if FIFOs enabled */
    outportb(rs_ss.fcr,'\x43'); /* clear RCVR FIFO */
  enable();

}

/* clear output buffer */
void rs_clrout(void)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;

  if(! rs_portopen) /* is a port open? */
    return;

  disable();
  rs_ds.out_tail = rs_ds.out_head;
  if(rs_ss.xmitfifo == 16) /* if FIFOs enabled */
    outportb(rs_ss.fcr,'\x45'); /* clear XMIT FIFO */
  enable();

}

/* use BIOS keyboard buffer head and tail pointers to determine if a key
   has been pressed */
int rs_keyhit(void)
{

  volatile int far* rs_keytail;
  volatile int far* rs_keyhead;

  rs_keyhead = (int far*)MK_FP(0x40,0x1A);
  rs_keytail = rs_keyhead + 1;

  if(*rs_keytail == *rs_keyhead)
    return 0;
  else
    return 1;

}

/* use BIOS data area tick count to as 10/182 sec. resolution timer.  If
   rs_cmd is 0, clear counter and return count up to that point.  Id
   rs_cmd is non-zero, return tick count from the point which timer is
   last cleared. */
unsigned rs_timer(int rs_cmd)
{

  volatile unsigned long far* rs_biosticks;
  static long rs_startticks;
  unsigned rs_tmp;

  rs_biosticks = (unsigned long far*)MK_FP(0x40,0x6C);

  if(rs_cmd){
    if(*rs_biosticks >= rs_startticks)
      return((unsigned)(*rs_biosticks - rs_startticks));
    else
      return((unsigned)(*rs_biosticks + 1573040UL - rs_startticks));
    }
  else{
    if(*rs_biosticks >= rs_startticks)
      rs_tmp = (unsigned)(*rs_biosticks - rs_startticks);
    else
      rs_tmp = (unsigned)(*rs_biosticks + 1573040UL - rs_startticks);
    rs_startticks = *rs_biosticks;
    }

  return rs_tmp;

}

/* rs_setflow: rs_cmd = 0: Turn flow control off
               rs_cmd = 1: Set flow control to hardware - Parameter1
                           is hardware line to monitor.
               rs_cmd = 2: Set flow control to XON/XOFF - Parameter1
                           is character to use for XON, Parameter2
                           is character to use for XOFF.
               rs_cmd = 3: Return status of flow control - 0 = output
                           normal, 1 = output halted
               rs_cmd = 4: Insert control character in output stream.
                           Parameter1 is control character.
               rs_cmd = 5: Manipulate hardware line.  Parameter1 is bit
                           pattern corresponding to line to change.
                           If parameter2 is 0, line is turned off,
                           if 1, line is turned on*/
int rs_setflow(int rs_cmd,...)
{

  extern struct rs_statics rs_ss;
  extern volatile struct rs_dynamics rs_ds;
  extern int rs_portopen;
  int rs_ret = 0;
  unsigned char rs_cntrl;
  va_list rs_ap;

  va_start(rs_ap, rs_cmd);

  if(rs_portopen){
    switch(rs_cmd){
      case 0: /* turn flow control off */
        rs_ss.flow = rs_cmd;
        break;
      case 1: /* set flow control to hardware */
        rs_ss.flow = rs_cmd;
        rs_ss.hdw_flw = ((char)(va_arg(rs_ap,int))) << 4;
        if((inportb(rs_ss.msr)) & rs_ss.hdw_flw)
          rs_ds.ier_msk = '\x0F';
        else
          rs_ds.ier_msk = '\x0D';
        break;
      case 2: /* set flow control to XON/XOFF */
        rs_ss.flow = rs_cmd;
        rs_ss.xon = (char)(va_arg(rs_ap,int));
        rs_ss.xoff = (char)(va_arg(rs_ap,int));
        break;
      case 3: /* return status of flow control */
        rs_ret = (rs_ds.ier_msk == '\x0D');
        break;
      case 4: /* insert control character in output stream */
        rs_cntrl = (char)(va_arg(rs_ap,int));
#ifndef RS_POLLED_XMIT
        outportb(rs_ss.ier,'\x0D');
#endif
        while(!(inportb(rs_ss.lsr) & '\x20'))
          ;
        outportb(rs_ss.thr,rs_cntrl);
#ifndef RS_POLLED_XMIT
        outportb(rs_ss.ier,rs_ds.ier_msk);
#endif
        break;
      }
    }
  else /* no port open */
    rs_ret = -1;

  va_end(rs_ap);

  return rs_ret;

}
