// ******************** START OF BIOSPORT.CPP ********************
//
// This file contains all of the code used by the BIOSPort and
// EBIOSPort classes.
//

#include <stdio.h>
#include <ctype.h>
#include "rs232.h"
#include "biosport.h"
#include "_8250.h"

// The BIOSPort constructor doesn't have very much work to do.  It
// sets all the saved settings to invalid values, since none of them
// can be accessed.  It has to set up the debug output, then set all
// of the port settings, and then return.  Note that there is no way
// to detect an error when attempting to access a BIOS port, so the
// constructor always succeeds.

BIOSPort::BIOSPort( RS232PortName port,
                    long baud_rate,
                    char parity,
                    int word_length,
                    int stop_bits,
                    int dtr,
                    int rts,
                    int xon_xoff,
                    int rts_cts,
                    int dtr_dsr )
{
    port_name = port;
    error_status = RS232_SUCCESS;

    first_debug_output_line = RS232::FormatDebugOutput();
    debug_line_count = FormatDebugOutput();
    saved_settings.BaudRate = -1L;
    saved_settings.Parity = '?';
    saved_settings.WordLength = -1;
    saved_settings.StopBits = -1;
    saved_settings.Dtr = -1;
    saved_settings.Rts = -1;
    saved_settings.XonXoff = -1;
    saved_settings.RtsCts = -1;
    saved_settings.DtrDsr = -1;
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     dtr,
                     rts,
                     xon_xoff,
                     rts_cts,
                     dtr_dsr );
    write_settings();
    line_status = 0;
}

// The void constructor is called by the inherited class EBIOSPort when
// it is being constructed.  Since EBIOSPort will initialize all the
// settings, this constructor just initializes the debug output.
// Note that this constructor is protected, since it doesn't create a
// properly initialized port that could be used as an object by itself.

BIOSPort::BIOSPort( void )
{
    first_debug_output_line = RS232::FormatDebugOutput();
    debug_line_count = FormatDebugOutput();
}

// There is no BIOS function to close a port, so the destructor
// doesn't have to do anything.

BIOSPort::~BIOSPort( void )
{
}

// The write_settings function is fairly limited when it comes to
// options.  There are lots of opportunities for errors here.
// Note that if the constructor attempts an invalid settings, the
// error code never gets returned to the calling program, since
// the constructor doesn't return a value.

RS232Error BIOSPort::write_settings( void )
{
    union REGS r;
    RS232Error status = RS232_SUCCESS;
    r.x.dx = port_name;
    r.h.ah = 0;
    r.h.al = 0;
    switch ( toupper( settings.Parity ) ) {
        case 'E' : r.h.al |= 0x18; break;
        case 'O' : r.h.al |= 0x08; break;
        default  : settings.Parity = 'N';
                   status = RS232_ILLEGAL_PARITY_SETTING;
        case 'N' : r.h.al |= 0x00; break;
    }
    switch ( settings.StopBits ) {
        case 1  : r.h.al |= 0; break;
        default : settings.StopBits = 2;
                  status = RS232_ILLEGAL_STOP_BITS;
        case 2  : r.h.al |= 4; break;
    }
    switch ( settings.WordLength ) {
        case 5   : r.h.al |= 0; break;
        case 6   : r.h.al |= 1; break;
        case 7   : r.h.al |= 2; break;
        default  : settings.WordLength = 8;
                   status = RS232_ILLEGAL_WORD_LENGTH;
        case 8   : r.h.al |= 3; break;
    }
    switch ( settings.BaudRate ) {
        case 110L    : r.h.al |= 0x00; break;
        case 150L    : r.h.al |= 0x20; break;
        case 300L    : r.h.al |= 0x40; break;
        case 600L    : r.h.al |= 0x60; break;
        case 1200L   : r.h.al |= 0x80; break;
        case 2400L   : r.h.al |= 0xa0; break;
        case 4800L   : r.h.al |= 0xc0; break;
        default      : settings.BaudRate = 9600L;
                       status = RS232_ILLEGAL_BAUD_RATE;
        case 9600L   : r.h.al |= 0xe0; break;
    }
    int86( 0x14, &r, &r );
    if ( settings.Dtr != -1 ) {
        settings.Dtr = -1;
        status = RS232_DTR_NOT_SUPPORTED;
    }
    if ( settings.Rts != -1 ) {
        settings.Rts = -1;
        status = RS232_RTS_NOT_SUPPORTED;
    }
    if ( settings.RtsCts != -1 ) {
        settings.RtsCts = -1;
        status = RS232_RTS_CTS_NOT_SUPPORTED;
    }
    if ( settings.DtrDsr != -1 ) {
        settings.DtrDsr = -1;
        status = RS232_DTR_DSR_NOT_SUPPORTED;
    }
    if ( settings.XonXoff != -1 ) {
        settings.XonXoff = -1;
        status = RS232_XON_XOFF_NOT_SUPPORTED;
    }
    return status;
}

// If the data ready bit is set in the UART LSR, this routine calls
// the BIOS function to read the character.  Otherwise it returns
// a timeout.  Even if the data ready bit is set, this routine is still
// vulnerable to hanging up in a several second delay if the
// incoming DSR line is not set.

int BIOSPort::read_byte( void )
{
    union REGS r;

    if ( error_status < 0 )
        return error_status;
    r.h.ah = 3;
    r.x.dx = port_name;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    if ( r.h.ah & LSR_DATA_READY ) {
        r.h.ah = 2;
        r.x.dx = port_name;
        int86( 0x14, &r, &r );
        line_status |= r.h.ah;
        if ( ( r.h.ah & 0x80 ) == 0 )
            return( r.h.al );
    }
    return( RS232_TIMEOUT );
}

// This function also uses a standard BIOS function call.  It is also
// vulnerable to a delay in the event that DSR or CTS are not set.

int BIOSPort::write_byte( int c )
{
    union REGS r;

    if ( error_status < 0 )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 0x01;
    r.h.al = (char) c;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    if ( r.h.ah & 0x80 )
        return RS232_TIMEOUT;
    return RS232_SUCCESS;
}

RS232Error BIOSPort::Set( long baud_rate,
                          int parity,
                          int word_length,
                          int stop_bits )
{
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED );
    return write_settings();
}

// The next four routines all execute the BIOS call that reads
// in the MSR.  The appropriate bit is then masked out, and the
// result is returned to the calling routine.

int BIOSPort::Cd( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    return ( r.h.al & MSR_CD ) != 0;
}

int BIOSPort::Ri( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    return ( r.h.al & MSR_RI ) != 0;
}

int BIOSPort::Cts( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    return ( r.h.al & MSR_CTS ) != 0;
}

int BIOSPort::Dsr( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    return ( r.h.al & MSR_DSR ) != 0;
}

// The four routines that check line status bits operate almost
// identically to the modem status routines.  The only difference
// is that the line status bits are accumulated in a private data
// member, so instead of just checking the bit in the LSR, these
// routines have to check the current state ORed with the cumulative
// state.  In addition, each of the routines has the option of
// either leaving the bit set, or clearing it after reading it.

int BIOSPort::ParityError( int reset )
{
    union REGS r;
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    status = ( line_status & LSR_PARITY_ERROR ) != 0;
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~LSR_PARITY_ERROR;
    return status;
}

int BIOSPort::BreakDetect( int reset )
{
    union REGS r;
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    status = ( line_status & LSR_BREAK_DETECT ) != 0;
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~LSR_BREAK_DETECT;
    return status;
}

int BIOSPort::FramingError( int reset )
{
    union REGS r;
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    status = ( line_status & LSR_FRAMING_ERROR ) != 0;
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~LSR_FRAMING_ERROR;
    return status;
}

int BIOSPort::HardwareOverrunError( int reset )
{
    union REGS r;
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    status = ( line_status & LSR_OVERRUN_ERROR ) != 0;
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~LSR_OVERRUN_ERROR;
    return status;
}

// The formatted debug output for BIOSPort is sparse.  It prints out
// two lines of information that dump the states of the MSR and LSR.

int BIOSPort::FormatDebugOutput( char *buffer, int line_number )
{
    if ( buffer == 0 )
        return( first_debug_output_line +  2 );
    if ( line_number < first_debug_output_line )
        return RS232::FormatDebugOutput( buffer, line_number );
    switch( line_number - first_debug_output_line ) {
        case 0 :
            sprintf( buffer,
                     "Derived class: BIOSPort  "
                     "RI: %2d  CD: %2d  CTS: %2d  DSR: %2d",
                     Ri(), Cd(), Cts(), Dsr() );
            break;
        case 1 :
            Ri();
            sprintf( buffer,
                     "Parity Err: %d  "
                     "Break Det: %d  "
                     "Overrun Err: %d  "
                     "Framing Err: %d",
                     ( line_status & LSR_PARITY_ERROR ) ? 1 : 0,
                     ( line_status & LSR_BREAK_DETECT ) ? 1 : 0,
                     ( line_status & LSR_OVERRUN_ERROR ) ? 1 : 0,
                     ( line_status & LSR_FRAMING_ERROR ) ? 1 : 0 );
            break;
        default :
            return RS232_ILLEGAL_LINE_NUMBER;
    }
    return RS232_SUCCESS;
}

// The read_buffer routine is set up to continue reading in data
// as long as the data ready bit is set in the LSR.  With a true
// BIOS, this means that it will generally only read in a single
// byte before returning to the Read() routine.  It is up to the
// calling program to make sure a time value is set up so that
// multiple bytes can be read in.  Note that a BIOS emulation system
// will usually be able to feed in an entire buffer with a single
// call.

int BIOSPort::read_buffer( char *buffer, unsigned int count )
{
    union REGS rin;
    union REGS rout;

    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    rin.x.dx = port_name;
    while ( count > 0 ) {
        rin.h.ah = 3;
        int86( 0x14, &rin, &rout );
        line_status |= rout.h.ah;
        if ( ( rout.h.ah & LSR_DATA_READY ) == 0 )
            break;
        rin.h.ah = 2;
        int86( 0x14, &rin, &rout );
        line_status |= rout.h.ah;
        if ( rout.h.ah & 0x80 )
            break;
        *buffer++ = rout.h.al;
        count--;
        ByteCount++;
    }
    *buffer = '\0';
    if ( count > 0 )
        return RS232_TIMEOUT;
    else
        return RS232_SUCCESS;
}

// Like read_buffer(), the write_buffer routine will usually only
// be able to write a single byte when using a true BIOS
// implementation.  It is up to the caller to invoke Write() with
// a long enough time delay to be able to send the entire buffer.
// A BIOS emulation system will usually be able to take in the
// entire buffer with one call to write_buffer().

int BIOSPort::write_buffer( char *buffer, unsigned int count )
{
    union REGS rin;
    union REGS rout;

    rin.x.dx = port_name;
    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    while ( count > 0 ) {
        rin.h.ah = 3;
        int86( 0x14, &rin, &rout );
        line_status |= rout.h.ah;
        if ( ( rout.h.ah & LSR_THRE ) == 0 )
            break;
        rin.h.ah = 1;
        rin.h.al = (char) *buffer++;
        int86( 0x14, &rin, &rout );
        if ( rout.h.ah & 0x80 )
            break;
        line_status |= rout.h.ah;
        buffer++;
        count--;
        ByteCount++;
    }
    if ( count > 0 )
        return RS232_TIMEOUT;
    else
        return RS232_SUCCESS;
}

// If the THRE bit is set, there is room for one byte, if clear, there
// is room for 0 bytes.

int BIOSPort::TXSpaceFree( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    if ( r.h.ah & LSR_THRE )
        return 1;
    else
        return 0;
}

// If the data ready bit is set, there is one byte present in the
// buffer, else 0.

int BIOSPort::RXSpaceUsed( void )
{
    union REGS r;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    r.x.dx = port_name;
    r.h.ah = 3;
    int86( 0x14, &r, &r );
    line_status |= r.h.ah;
    if ( r.h.ah & LSR_DATA_READY )
        return 1;
    else
        return 0;
}

// EBIOSPort has a few extra functions above and beyond BIOSPort.
// It has the ability to read in the old state of DTR and RTS,
// and can send a break signal.  It also has a few extra baud rates
// and other line settings.

EBIOSPort::EBIOSPort( RS232PortName port,
                      long baud_rate,
                      char parity,
                      int word_length,
                      int stop_bits,
                      int dtr,
                      int rts,
                      int xon_xoff,
                      int rts_cts,
                      int dtr_dsr )
{
    port_name = port;
    error_status = RS232_SUCCESS;

    first_debug_output_line = BIOSPort::FormatDebugOutput();
    debug_line_count = FormatDebugOutput();
    read_settings();
    saved_settings = settings;
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     dtr,
                     rts,
                     xon_xoff,
                     rts_cts,
                     dtr_dsr );
    break_on = 0;
    write_settings();
    line_status = 0;
}

EBIOSPort::~EBIOSPort( void )
{
}

// The only additional information that I have to offer with this
// output routine is the setting of the break flag, which will
// usually be clear.

int EBIOSPort::FormatDebugOutput( char *buffer, int line_number )
{
    if ( buffer == 0 )
        return( first_debug_output_line +  1 );
    if ( line_number < first_debug_output_line )
        return BIOSPort::FormatDebugOutput( buffer, line_number );
    switch( line_number - first_debug_output_line ) {
        case 0 :
            sprintf( buffer,
                     "Derived class: EBIOSPort  "
                     "Break flag: %2d",
                     break_on );
            break;
        default :
            return RS232_ILLEGAL_LINE_NUMBER;
    }
    return RS232_SUCCESS;
}

// There are exactly two settings that I can read here:  the state
// of Dtr and Rts.  It isn't much, but I read them both in and
// set the rest of the values to be invalid.

void EBIOSPort::read_settings( void )
{
    union REGS r;

    r.x.dx = port_name;
    r.h.ah = 5;
    r.h.al = 0;
    int86( 0x14, &r, &r );
    settings.Dtr = ( ( r.h.bl & 0x1 ) != 0 );
    settings.Rts = ( ( r.h.bl & 0x2 ) != 0 );
    settings.BaudRate = -1L;
    settings.Parity = '?';
    settings.WordLength = -1;
    settings.StopBits = -1;
    settings.XonXoff = -1;
    settings.RtsCts = -1;
    settings.DtrDsr = -1;
}

// write_settings() uses a different function for the EBIOS, so it has
// a few extra settings to support.

RS232Error EBIOSPort::write_settings( void )
{
    union REGS r;
    RS232Error status = RS232_SUCCESS;

    r.x.dx = port_name;
    r.h.ah = 4;
    if ( break_on )
        r.h.al = 1;
    else
        r.h.al = 0;
    switch ( toupper( settings.Parity ) ) {
        case 'E' : r.h.bh = 1; break;
        case 'O' : r.h.bh = 2; break;
        default  : settings.Parity = 'N';
                   status = RS232_ILLEGAL_PARITY_SETTING;
        case 'N' : r.h.bh = 0; break;
    }
    switch ( settings.StopBits ) {
        case 1  : r.h.bl = 0; break;
        default : settings.StopBits = 2;
                  status = RS232_ILLEGAL_STOP_BITS;
        case 2  : r.h.bl = 1; break;
    }
    switch ( settings.WordLength ) {
        case 5   : r.h.ch = 0; break;
        case 6   : r.h.ch = 1; break;
        case 7   : r.h.ch = 2; break;
        default  : settings.WordLength = 8;
                   status = RS232_ILLEGAL_WORD_LENGTH;
        case 8   : r.h.ch = 3; break;
    }
    switch ( settings.BaudRate ) {
        case 110L    : r.h.cl = 0x00; break;
        case 150L    : r.h.cl = 0x01; break;
        case 300L    : r.h.cl = 0x02; break;
        case 600L    : r.h.cl = 0x03; break;
        case 1200L   : r.h.cl = 0x04; break;
        case 2400L   : r.h.cl = 0x05; break;
        case 4800L   : r.h.cl = 0x06; break;
        default      : settings.BaudRate = 9600L;
                       status = RS232_ILLEGAL_BAUD_RATE;
        case 9600L   : r.h.cl = 0x07; break;
        case 19200L  : r.h.cl = 0x08; break;
    }
    int86( 0x14, &r, &r );
    r.x.dx = port_name;
    r.h.ah = 5;
    r.h.al = 1;
    r.h.bl = (unsigned char) ( ( settings.Dtr ) ? 1 : 0 );
    r.h.bl |= ( settings.Rts ) ? 2 : 0;
    int86( 0x14, &r, &r );
    if ( settings.RtsCts != -1 ) {
        settings.RtsCts = -1;
        status = RS232_RTS_CTS_NOT_SUPPORTED;
    }
    if ( settings.DtrDsr != -1 ) {
        settings.DtrDsr = -1;
        status = RS232_DTR_DSR_NOT_SUPPORTED;
    }
    if ( settings.XonXoff != -1 ) {
        settings.XonXoff = -1;
        status = RS232_XON_XOFF_NOT_SUPPORTED;
    }
    return status;
}

// EBIOSPort can set Dtr and Rts, BIOSPort can't.

int EBIOSPort::Dtr( int setting )
{
    if ( setting != UNCHANGED ) {
        settings.Dtr = ( setting != 0 );
        write_settings();
    }
    return ( settings.Dtr != 0 );
}

int EBIOSPort::Rts( int setting )
{
    if ( setting != UNCHANGED ) {
        settings.Rts = ( setting != 0 );
        write_settings();
    }
    return ( settings.Rts != 0 );
}

int EBIOSPort::Break( long milliseconds )
{
    long timer;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    timer = ReadTime() + milliseconds;
    break_on = 1;
    write_settings();
    while ( ReadTime() < timer )
        IdleFunction();
    break_on = 0;
    write_settings();
    return RS232_SUCCESS;
}

RS232Error EBIOSPort::Set( long baud_rate,
                           int parity,
                           int word_length,
                           int stop_bits )
{
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED );
    return write_settings();
}
