/*
        S E R I A L . C

        Copyright (c) 1988, 1989 by Oklahoma Software Systems

        This code was written to allow a good set of interrupt driven
        serial routines to be available for Microsoft C 5.x under MS/PC-DOS
        3.x or higher.  These routines are nothing fancy; but they will
        support X and Ymodem transfer algorithms (I have them here) using
        either Checksum or CRC-16 error-checking.

        If you have any questions you can contact me via EchoMAIL at 147/30
        where I operate as a Point-mail system.

        Ron Merts


*/

#include        "serial.h"
#include        <dos.h>
#include        <stdio.h>
#include        <conio.h>
#include        <bios.h>
#include        <process.h>

int SError;
int portbase=0;
void (interrupt far *oldvects[3])();
char ccbuf[SBUFSIZ];
int startbuf=0;
int endbuf=0;

/*      this does the hard work (handling interrupts)           */

void far interrupt com_int (void)

{
    _disable ();
    if ((inp (portbase+IIR) & RX_MASK)==RX_ID) {
        if (((endbuf+1) & 0x1fff)==startbuf)
            SError=BUFOVFL;
        ccbuf[endbuf++]=inp (portbase+RX);
        endbuf&=0x1fff;
        };
    outp (ICR,EOI);
    _enable ();
}

/* this routine returns the currently value in the buffer */

int getccb(void)

{
    int res;
    if (endbuf==startbuf) return (-1);
    res=(int) ccbuf[startbuf];
    startbuf=(startbuf+1) % SBUFSIZ;
    return (res);
}

void setvects (void)

{
    oldvects[0]= _dos_getvect(0x0b);
    oldvects[1]=  _dos_getvect(0x0c);
    _dos_setvect(0x0b,com_int);
    _dos_setvect(0x0c,com_int);
}

void resvects (void)

{
    _dos_setvect(0x0b,oldvects[0]);
    _dos_setvect(0x0c,oldvects[1]);
}

void i_enable (int pnum)

{
    int c;
    _disable();
    c = inp (portbase+MCR) | MC_INT;
    outp (portbase+MCR,c);
    outp (portbase+IER,RX_INT);
    c = inp (IMR) & (pnum==2 ? IRQ3 : IRQ4);
    outp (IMR,c);
    _enable();
}

void i_disable (void)

{
    int c;
    _disable ();
    c=inp (IMR) | ~IRQ3 | ~IRQ4;
    outp (IMR,c);
    outp (portbase + IER,0);
    c=inp (portbase+MCR) & ~MC_INT;
    outp (portbase+MCR,c);
    _enable ();
}

void comon (void)

{
    int c,pnum;
    pnum = portbase == COM1BASE ? 1 : 2;
    i_enable (pnum);
    c=inp (portbase + MCR) | DTR | RTS;
    outp (portbase + MCR,c);
}

void initserial (void)

{
    endbuf=startbuf=0;
    setvects();
    comon();
};

void comoff (void)

{
    i_disable ();
    outp (portbase+MCR,0);
}

void closeserial(void)

{
    comoff();
    resvects();
};

/* this outputs a serial character              */

int SerialOut (char x)

{
    long timeout = 0x0000ffff;
    outp (portbase+MCR,OUT2|DTR|RTS);

    /* wait for clear to send     */
    while ((inp(portbase + MSR) & CTS)==0)
        if ((--timeout)==0) return (-1);
    timeout=0x0000ffff;

    /* wait for outport register to clear         */
    while ((inp(portbase+LSR) & DSR)==0)
        if ((--timeout)==0) return (-1);
            _disable ();
    outp (portbase+TX,x);
    _enable ();
    return (0);
}

/* this routine sets which port we are working with     */

int SetPort (int Port)

{
    char far * RS232_Addr;
    int Offset;

    switch (Port) {                      /* sort out the base address    */
        case 1 :
             Offset=0x0000;
             break;                         /* only ports one & two allowed */
         case 2 :
             Offset=0x0002;
             break;
         default :
             return (-1);
         };
    FP_OFF(RS232_Addr) = Offset;          /* find out where the port is   */
    FP_SEG(RS232_Addr) = 0x0040;
    if (*RS232_Addr==0)
        return (-1);      /* if it ain't there return (-1)*/
    switch (Port) {
        case 1:
            portbase=0x03F8;
            break;
        case 2:
            portbase=0x02F8;
            break;
        };
    return (0);
}

/* this routine sets the speed; will accept funny baud rates   */

int SetSpeed (int Speed)

{
    char c;
    int divisor;
    if (Speed==0)
        return (-1);            /* avoid divide by zero */
    else
        divisor=(int)(115200L/Speed);
    if (portbase==0)
        return (-11);
    _disable ();
    c=inp (portbase+LCR);
    outp (portbase+LCR,(c|0x80));                     /* set DLAB     */
    outp (portbase+DLL,(divisor & 0x00ff));   /* set divisor  */
    outp (portbase+DLH,((divisor>>8)&0x00ff));
    outp (portbase+LCR,c);
    _enable();
    return (0);
}

/*      This routine set the LCR                */

int SetOthers (int Parity,int Bits,int StopBit)

{
    int temp;
    if (portbase==0)
        return (-1);
    if ((Parity<NO_PAR) || (Parity>OD_PAR))
        return (-1);
    if ((Bits<5) || (Bits>8))
        return (-1);
    if ((StopBit<1) || (StopBit>2))
        return (-1);
    temp=Bits-5;
    temp|=((StopBit==1) ? 0x00 : 0x04);
    switch (Parity) {
        case NO_PAR:
            temp |= 0x00;
            break;
        case OD_PAR:
            temp |= 0x08;
            break;
        case EV_PAR:
            temp |= 0x18;
            break;
        }
    _disable();            /* turn off interrupts */
    outp (portbase+LCR,temp);
    _enable();             /* turn em back on */
    return (0);
}

/*      This routine sets the ports                     */

int setserial (int Port,int Speed,int Parity,int Bits,int StopBit)

{
    if (SetPort(Port)==-1)
        return(-1);
    if (SetSpeed(Speed)==-1)
        return(-1);
    if (SetOthers(Parity,Bits,StopBit)==-1)
        return(-1);
    return(0);
}

/*      This routine check for Carrier Detect,
          returns 1 if CD present or
          returns 0 if CD not present
*/

int checkCD()
{
    int c;
    _disable();    /* disable Interrupts */
    c = inp(portbase+MSR) & CD;
    if (c == 128)
        c = 1;
    _enable();     /* Enable Interrupts again */
    return(c);
}



/* short demo                                           */
/* this opens the ports and echos to screen until       */
/* Ctrl-Z received                                      */

main (argc, argv)
int argc;
char **argv;

{
    int c, c_m, bd_rate, com_port_num;

    if (argc != 3) {
        printf("USAGE:  serial <1 | 2> <Baud-rate>\n\n\n");
        printf("serial 2 2400    - Sets COM2 to 2400 Baud\n");
        printf("serial 1 1200    - Sets COM1 to 1200 Baud\n");
        exit(1);
        }
    com_port_num = atoi(argv[1]);
    if (com_port_num != 1 && com_port_num != 2) {
        printf("Give me a break, 1 or 2 only please!\n\n");
        exit(1);
        }
    bd_rate = atoi(argv[2]);
    switch (bd_rate) {
        case  300:
        case 1200:
        case 2400:
        case 4800:
        case 9600:
            break;
        default:
            printf("Are you trying to be difficult????\n");
            printf(" Valid baud rates are 300,1200,2400,4800 and 9600\n\n");
            exit(1);
        }
    if (setserial (com_port_num,bd_rate,NO_PAR,8,1) ==-1)
        exit(1);
    initserial();
    printf("You're now in terminal mode.  Press ESC to quit the program.\n\n");

    do {
        if (kbhit()) {         /* Check local keyboard */
            c = getch();       /* If we got something */
            if (c==27)
                break;
            SerialOut(c);      /* Send it to the COM port */
            }

        if ((c_m=getccb())!=-1)  /* Check the COM port, -1 means nothing there */
            putchar (c_m);       /* If we got something, print it */

        } while (c!=27);

    closeserial();
}
