/////////////////////////////////////////////////////////////////////////////
//
//  Asynchronous Class for C++
//  Copyright (C) 1991 by Jui-Lin Hung and SPD!
//
//  You may freely use or incorporate these routines into your own programs
//  without royalty to me, as I believe this is beneficial to programmers.
//  However, I would like to request that if you distribute the source code,
//  you would include this header in the source file and not remove it.
//  Of course, I would have no way of knowing, but I am appealing to your
//  sense of good will and morality.  Thank you, and I hope these routines
//  are useful.
//
//  April, 1991 - Version 1.0
//
/////////////////////////////////////////////////////////////////////////////


#include <dos.h>

#include "asynch.h"     // Asynch class definitions


//---------------------------------------------------------------------------


_asynch_info _ainfo;    // Global asynchronous info variable


//---------------------------------------------------------------------------


void far interrupt (*OldVect)(...);

void far interrupt asynch_irq(...)
// Asynchronous port interrupt handler.  This is the new interrupt which
// we will swap with the old one to handle all asynchronous i/o
{
    static int temp;

		disable();
		enable();                               // enable interrupts
		for (;;)
		{
				temp = inportb(_ainfo.base+IIR);    // why interrupt was called
				if (temp & 0x01)                    // Nothing else to do
				{
						outportb(ICR,EOI);              // reset interrupt
						return;                         // return to program
				}
				switch(temp)
        {
            case 0x00:  // modem status changed
                inportb(_ainfo.base+MSR);   // read in useless char
                break;
            case 0x02:  // Request To Send char
                if (_ainfo.outhead != _ainfo.outtail)  // there's a char to send
                {
                    // send the character
                    outportb(_ainfo.base+TXR,_ainfo.outbuf[_ainfo.outhead++]);
                    // if at end of buffer, reset pointer
                    if (_ainfo.outhead == OBUF_LEN) _ainfo.outhead=0;
                }
                break;
            case 0x04:  // character ready to be read in
								// read character into inbuffer
								_ainfo.inbuf[_ainfo.inhead++] = inportb(_ainfo.base+RXR);
								if (_ainfo.inhead == IBUF_LEN) // if at end of buffer
										_ainfo.inhead=0;           // reset pointer
								break;
						case 0x06:  // line status has changed
								inportb(_ainfo.base+LSR);     // read in useless char
								break;
				}
		}
}


void Asynch::asynchInit()
// Initializes variables, saves old interrupt vectors and sets the new
// interrupts.  Initializes the asynchronous port too.
{
    _ainfo.inhead=_ainfo.intail=0;          // reset in buffer pointers
    _ainfo.outhead=_ainfo.outtail=0;        // reset out buffer pointers
    _ainfo.flow = 0;                        // Default flow control is off
    _ainfo.nohangup = 0;                    // hangup when done

    OldVect = getvect(_ainfo.irq+MCI);      // Save old interrupt vector
    setvect(_ainfo.irq+MCI,asynch_irq);     // Set up serial int handler
    outportb(_ainfo.base+LCR, 0x03);        // Turn DTR and RTS on
    disable();                              // disable ints during init
    int temp = inportb(_ainfo.base+LSR);    // read serial port line status
    temp = inportb(_ainfo.base+RXR);        // read char
    temp = inportb(IMR);                    // get interrupt settings
    temp = temp & ((IER<<_ainfo.irq)^0xff); // turn on serial interrupt
    outportb(IMR, temp);                    // send out new int settings
    outportb(_ainfo.base+IER, IER);         // turn on all IRQ events
    outportb(_ainfo.base+MCR, inportb(_ainfo.base+MCR) | 0x0a);
    enable();                               // re-enable all interrupts
}


Asynch::Asynch(unsigned char p)
// Class constructor - set base address and irq number.
{
    switch(p)   // set up correct base address and irq for this port
    {
        case COM1:  // serial port 1
            _ainfo.base = 0x03f8;   // base address for port 1
            _ainfo.irq  = 4;        // interrupt number for port 1
            break;
        case COM2:  // serial port 2
            _ainfo.base = 0x02f8;   // base address for port 2
            _ainfo.irq  = 3;        // interrupt number for port 2
            break;
        case COM3:  // serial port 3
            _ainfo.base = 0x03e8;   // base address for port 3
            _ainfo.irq  = 4;        // interrupt number for port 3
            break;
        case COM4:  // serial port 4
            _ainfo.base = 0x02e8;   // base address for port 4
            _ainfo.irq  = 3;        // interrupt number for port 4
            break;
        default:  // defaults to com2
            _ainfo.base = 0x02f8;   // base address for port 2
            _ainfo.irq  = 3;        // interrupt number for port 2
            break;
    }

    _ainfo.baud = 2400;             // Default baud rate is 2400

    asynchInit();                   // call interrupt initialization
    setBaud(_ainfo.baud);           // set to default baud rate
}


Asynch::Asynch(unsigned char p,unsigned int b)
// Class constructor - set base address and irq number.
// This particular constructor also sets the baud rate to the
// specified speed
{
    switch(p)   // set up correct base address and irq for this port
    {
        case COM1:  // serial port 1
            _ainfo.base = 0x03f8;   // base address for port 1
            _ainfo.irq  = 4;        // interrupt number for port 1
            break;
        case COM2:  // serial port 2
            _ainfo.base = 0x02f8;   // base address for port 2
            _ainfo.irq  = 3;        // interrupt number for port 2
            break;
        case COM3:   // serial port 3
            _ainfo.base = 0x03e8;   // base address for port 3
            _ainfo.irq  = 4;        // interrupt number for port 3
            break;
        case COM4:  // serial port 4
            _ainfo.base = 0x02e8;   // base address for port 4
            _ainfo.irq  = 3;        // interrupt number for port 4
            break;
        default:    // defaults to com2
            _ainfo.base = 0x02f8;   // base address for port 2
            _ainfo.irq  = 3;        // interrupt number for port 2
            break;
    }

    _ainfo.baud = b;                // Default baud rate is 2400

		asynchInit();                   // call interrupt initialization
    setBaud(_ainfo.baud);           // set to default baud rate
}



Asynch::~Asynch()
// Class destructor - de-initializes the asynchronous port, and restores
// the old interrupts
{
    disable();                          // disable interrupts
    int temp = inportb(IMR);
    temp = temp | (IER << _ainfo.irq);
    outportb(IMR, temp | CTS);
    outportb(_ainfo.base+IIR, 0x00);
    outportb(_ainfo.base+MCR, IER);
    enable();                           // enable interrupts
    setvect(_ainfo.irq+MCI,OldVect);    // reinstate old vector
}


void Asynch::setBaud(unsigned int b)
// Sets the baud rate to the specified speed
{
    if (b > 49 && (long)b < 57601L)
    {
        float rate = (float)115200.0 / ((float)b);
        _ainfo.baud = (unsigned int)rate;

        outportb(_ainfo.base+LCR, inportb(_ainfo.base+LCR) | 0x80);
        outportb(_ainfo.base, (_ainfo.baud & 0x00ff));
        outportb(_ainfo.base+IER, ((_ainfo.baud >> MCI) & 0x00ff));
        outportb(_ainfo.base+LCR, inportb(_ainfo.base+LCR) & 0x7f);
    }
}


int Asynch::dtr()
// Returns 1 if the DTR is high, or if DTR remains low, returns a 0
{
    int addr;

    switch(_ainfo.port)
    {
        case COM1: addr = 0x03fe; break;
        case COM2: addr = 0x02fe; break;
        case COM3: addr = 0x03ee; break;
        case COM4: addr = 0x02ee; break;
    }
    if (inportb(addr) & 128)
        return(1);
    delay(500);
    return(inportb(addr) & 128);
}


void Asynch::setDtr()
// This function basically sets the DTR to high.
{
    outportb(_ainfo.base+MCR, inportb(_ainfo.base+MCR) & 0x00fe);
}


void Asynch::dropDtr()
// This function basically sets the DTR to low (i.e. hangs up the phone)
{
    int addr;

		if (!_ainfo.nohangup)
    {
        switch(_ainfo.port)
        {
            case COM1: addr = 0x03fc; break;
            case COM2: addr = 0x02fc; break;
            case COM3: addr = 0x03ec; break;
            case COM4: addr = 0x02ec; break;
        }
        outportb(addr, inportb(addr) & 0xfe);
        delay(500);
    }
}    


int Asynch::inCount()
// Counts how many characters are remaining to be read in from the input
// buffer
{
		disable();                          // disable interrupts
		int len = _ainfo.intail - _ainfo.inhead;
		enable();                           // enable interrupts

		return((len>0) ? IBUF_LEN-len-1 : (-len));
		//return((len>0) ? len:(-len));
}


int Asynch::outCount()
// Counts how many characters are remaining to be outputted in the output
// buffer
{
    int len = _ainfo.outtail - _ainfo.outhead;
		return((len>0) ? IBUF_LEN-len-1 : (-len));
		//return((len>0) ? len:(-len));
}


void Asynch::flushInBuf()
// Flushes the input buffer
{
    disable();                          // disable interrupts
    _ainfo.inhead=_ainfo.intail=0;      // reset pointers (dump buffer)
    enable();                           // enable interrupts
}


void Asynch::flushOutBuf()
// Flushes the output buffer
{
		disable();                          // disable interrupts
		_ainfo.outhead=_ainfo.outtail=0;    // reset pointers (dump buffer)
		enable();                           // enable interrupts
}


Asynch &Asynch::operator<<(char ch)
// Inserts the character to be outputted into the output buffer, checking
// for an open slot in the output buffer array.  If there is, insert
// the character, or if there isn't, wait until a slot opens up.
{
//    if (ch)                         // If this is a valid char
		{
				enable();                   // turn on irqs to ensure data output

				// check buffer, and if full, wait for an available opening
				while((_ainfo.outhead-1==_ainfo.outtail) ||
							(_ainfo.outtail==OBUF_LEN-1 && _ainfo.outhead==0))
						;
				disable();  // make sure nothing happens while changing buffer
				_ainfo.outbuf[_ainfo.outtail++]=ch; // insert character into buffer;
				if (_ainfo.outtail == OBUF_LEN)     // if at end of out buffer
						_ainfo.outtail = 0;             // reset pointer
				enable();                           // re-enable interrupts
				outportb(_ainfo.base+DTR,0x0f);
		}
		return(*this);
}


Asynch &Asynch::operator<<(char *str)
// Outputs a string to the serial port
{
		while (*str)
		{
				if (*str=='\n')
						(*this) << '\r', (*this) << '\n';
				else
						(*this) << (*str);
				str++;
		}
		return(*this);
}


Asynch &Asynch::operator>>(char &ch)
// Returns either the character to be received from modem if there is one
// waiting in the buffer, or returns a 0 if there is no character waiting.
{
		disable();                          // disable irqs while getting char
		if (_ainfo.inhead != _ainfo.intail)     // there is a character
		{
				//disable();                          // disable irqs while getting char
				ch = _ainfo.inbuf[_ainfo.intail++]; // get character from buffer
				if (_ainfo.intail == IBUF_LEN)      // if at end of in buffer
						_ainfo.intail=0;                // reset pointer
				enable();                           // re-enable interrupt
				return(*this);                      // return the char
    }
    ch = 0;
		enable();                           // re-enable interrupt
		return(*this);                          // return nothing
}


// C type functions for asynchronous i/o
void Asynch::outCh(char ch)
// Inserts the character to be outputted into output buffer.  If there is
// an open slot in the output buffer array, insert character there, or
// wait for a slot to open.
{
    if (ch)                         // If this is a valid char
    {
        enable();                   // turn on irqs to ensure data output

        // check buffer, and if full, wait for an available opening
        while((_ainfo.outhead-1==_ainfo.outtail) ||
              (_ainfo.outtail==OBUF_LEN-1 && _ainfo.outhead==0))
            ;
        disable();  // make sure nothing happens while changing buffer
        _ainfo.outbuf[_ainfo.outtail++]=ch; // insert character into buffer;
        if (_ainfo.outtail == OBUF_LEN)     // if at end of out buffer
            _ainfo.outtail = 0;             // reset pointer
        enable();                           // re-enable interrupts
        outportb(_ainfo.base+DTR,0x0f);
    }
}


void Asynch::outStr(char *str)
// Outputs a string through the serial port
{
    while (*str)
    {
        if (*str=='\n')
            outCh('\r'), outCh('\n');
        else
            outCh(*str);
        str++;
    }
}


char Asynch::inCh()
// Returns 0 if no character waiting, or the character itself if there
// is a character waiting in the buffer
{
		disable();                          // disable irqs while getting char
		if (_ainfo.inhead != _ainfo.intail)     // there is a character
		{
				//disable();                          // disable irqs while getting char

				// get character from buffer
				char ch = _ainfo.inbuf[_ainfo.intail++];

				if (_ainfo.intail == IBUF_LEN)      // if at end of in buffer
						_ainfo.intail=0;                // reset pointer
				enable();                           // re-enable interrupt
				return(ch);                         // return the char
		}
		enable();                           // re-enable interrupt
		return(0);                              // return nothing
}
