#include <dos.h>
#include <stdio.h>
#define   COM_PORT          2   /* serial port definitions for COM2 */
#define   COMPARMS       0x83   /* comm. parameters for 1200-n-8-1  */
#include "serial.h"  /* definitions and constants for this program  */


main()      /* main program initializes the hardware, then enters a */
{        /* loop where it moves characters from receive serial port */
int ch;     /* to screen, and from keyboard to transmit serial port */

  install();      /* install interrupt handler, initialize COM port */
  atexit(cleanup);/* post removal routine to execute at program end */

  while( 1 ) {           /* main program loop, repeat over and over */

    while ((ch = sgetch()) != S_EOF) {  /* if serial char available */
      putch(ch);                        /* put it on the screen     */
      if (translation && ch == '\r')    /* if translation in effect */
        putch('\n');                    /* add line feeds to C/R's  */
    }

    if (kbhit()) { /* keyboard handler, execute if key has been hit */

      ch = getch();         /* read the character from the keyboard */
      if (ch == 0)          /* if zero, next character is scan code */
        switch (ch = getch()) { /* read scan code from the keyboard */

          case 0x12:                      /* if character is Alt-E  */
            echo = !echo;                 /* toggle the screen echo */
            if (echo) printf("\nEcho on.\n");
            else printf("\nEcho off.\n");
            break;

          case 0x14:                      /* if character is Alt-T  */
            translation = !translation;   /* toggle the translation */
            if (translation) printf("\nTranslation on.\n");
            else printf("\nTranslation off.\n");
            break;
                                          /* if character is Alt-S  */
          case 0x1f:                      /* print the modem status */
            ch = inportb(ACE_MODEM_STAT_REG); /* get current status */
            printf("\nStatus:  DCD=%1d DSR=%1d CTS=%1d\n",
              (ch & DCD) ? 1:0, (ch & DSR) ? 1:0, (ch & CTS) ? 1:0);
            if (break_detect || frame_error
                    || char_overrun || parity_error) {
                printf("Errors:\n");                /* error report */
                printf("  overrun      = %4d\n", char_overrun);
                printf("  frame error  = %4d\n", frame_error );
                printf("  break detect = %4d\n", break_detect);
                printf("  parity error = %4d\n", parity_error);
                break_detect = frame_error =
                    char_overrun = parity_error = 0;
            }
            else printf("No errors.\n");
            if (echo) printf("Echo on.\n");
            if (translation) printf("Translation on.\n");
            break;

          case 0x2d:                       /* if character is Alt-X */
            exit();                        /* terminate the program */
        }
      else {     /* otherwise, the character was a normal character */
        sputch(ch);         /* so send it to the serial transmitter */
        if (echo) {                      /* if echo is on           */
          putch(ch);                     /* echo char to the screen */
          if (ch == '\r') putch('\n');   /* add line feeds to C/R's */
        }
      }
    }                                    /* end of keyboard handler */

    if (ring_detect) {     /* if ring is detected, beep the speaker */
      putch('\a');         /* requires modem cable pin 22 connected */
      ring_detect = 0;     /* then reset ring detect for next time  */
    }
  }
}



void interrupt serial_isr(void)   /* interrupt service routine      */
{                                 /* executed when receive data is  */
char ch;                          /* available, or when line status */
int temp_index;                   /* or modem status changes        */

  enable();                       /* re-enable the other interrupts */

  ch = inportb(ACE_INT_IDENT_REG); /* examine IIR for cause of int. */
  switch (ch) {

    case 6:                            /* if line status interrupt  */
      ch = inportb(ACE_LINE_STAT_REG); /* read line status register */
      if (ch & PE) parity_error++;     /* then increment the error  */
      if (ch & OE) char_overrun++;     /* counters according to     */
      if (ch & FE) frame_error++;      /* which error bits are set  */
      if (ch & BI) break_detect++;
      inportb(ACE_DATA_REG);  /* read the data register to empty it */
      break;

    case 4:                         /* if received data available   */
      ch = inportb(ACE_DATA_REG);   /* read character from the ACE  */
      temp_index = input_index + 1; /* temporary value              */
      if (temp_index >= QUELEN)     /* test for index past buf. end */
        temp_index = 0;             /* if so, reset to beginning    */
      if (temp_index != output_index) { /* test for queue overflow  */
        queue[temp_index] = ch;         /* put character into queue */
        input_index = temp_index;       /* and update the index     */
      }              /* if the queue overflows, characters are lost */
      break;

    case 0:                            /* if modem status interrupt */
      ch = inportb(ACE_MODEM_STAT_REG);   /* read modem status reg. */
      if (ch & DCTS) {                  /* if clear to send changed */
                     /* a routine to handle CTS changes may go here */
      }
      if (ch & DDSR) {                 /* if data set ready changed */
                     /* a routine to handle DSR changes may go here */
      }
      if (ch & TERI) {       /* if trailing edge ring indicator was */
        ring_detect = 1;           /* asserted, turn on ring detect */
      }
      if (ch & DDCD) {            /* if data carrier detect changed */
                     /* a routine to handle DCD changes may go here */
      }
  }

  outportb(PIC_CTL_REG, NON_SPEC_EOI);         /* finally, send the */
                           /* non-specific EOI to re-enable the PIC */
}



int sgetch(void)   /* read a character from the serial input buffer */
{
char ch;
int temp_index;     /* temporary index value avoids a conflict with */
                    /* interrupt routine updating the same variable */

  if (output_index != input_index) {  /* test for char. available   */
    temp_index = output_index + 1;    /* increment temporary index  */
    if (temp_index >= QUELEN)         /* is index past buffer end ? */
      temp_index = 0;                 /* if so, reset to beginning  */
    ch = queue[temp_index];           /* get character from buffer  */
    output_index = temp_index;        /* update the output index    */
    return(ch);                       /* character is return value  */
  }
  else return(S_EOF);   /* if no chars available, return serial EOF */
}



void sputch(char ch)    /* output a character to serial transmitter */
{
  while( inportb(ACE_LINE_STAT_REG) & THRE == 0 ) /* buffer empty ? */
    ;             /* if transmit buffer not empty, wait, do nothing */
  outportb(ACE_DATA_REG, ch);  /* when empty, send char to the 8250 */
}



void install(void)    /* installs the interrupt service routine and */
{                 /* initializes the 8250 for active communications */
char ch;

  bioscom(0, COMPARMS, COM_PORT - 1); /* set communications params. */

  /* enable ACA modem status, line status & receive data interrupts */
  outportb(ACE_INT_ENB_REG, 0xd); /* enable modem/line/rcvdata ints */
  inportb(ACE_DATA_REG);          /* empty receive data register    */
  inportb(ACE_LINE_STAT_REG);     /* clear the line status register */

  old_vector = getvect(COM_INT_NUM);   /* save previous int. vector */
  setvect(COM_INT_NUM, *serial_isr);   /* set vector to our routine */

  ch = inportb(PIC_INT_MASK_REG);   /* get current interrupt mask   */
  ch &= IRQ_MASK;                   /* unmask our IRQ (set it to 0) */
  outportb(PIC_INT_MASK_REG, ch);   /* and write it back to the PIC */

  outportb(ACE_MODEM_CTL_REG, 0xb); /* set RTS, DTR to enable modem */
   /* and turn on OUT2 to enable the 8250's IRQ interrupt to system */

  printf("\nCOM%d initialized.\n", COM_PORT);    /* sign-on message */
}



void cleanup(void)   /* routine resets the serial port at exit time */
{
char ch;

  outportb(ACE_INT_ENB_REG, 0);      /* disable all 8250 interrupts */

  outportb(ACE_MODEM_CTL_REG, 0);/* clear RTS, DTR to disable modem */
      /* and turn off OUT2 to disable the 8250's IRQ int. to system */

  ch = inportb(PIC_INT_MASK_REG);   /* get current interrupt mask   */
  ch |= !IRQ_MASK;                  /* mask our IRQ (set it to 1)   */
  outportb(PIC_INT_MASK_REG, ch);   /* and write it back to the PIC */

  setvect(COM_INT_NUM, old_vector); /* restore previous int. vector */

  printf("\nProgram terminated.\n\n");          /* sign-off message */
}



/* end of program */
