//
//  PC8250.CPP
//
//  Source code from:
//
//  Serial Communications: A C++ Developer's Guide, 2nd Edition
//  by Mark Nelson, IDG Books, 1999
//
//  Please see the book for information on usage.
//
//  This file contains most of the code used in the PC8250
//  class.  The remainder of the code can be found in
//  ISR_8250.CPP, which has the ISR and its support code.
//

#include <stdio.h>
#include <dos.h>
#include <ctype.h>
#include <conio.h>

#include "rs232.h"
#include "pc8250.h"
#include "_pc8250.h"
#include "ascii.h"

// Data used to initialize UART addresses and IRQ lines.

static int Uarts[]            = { 0x3f8,  0x2f8,  0x3e8,  0x2e8 };
static enum irq_name IRQs[]   = { IRQ4,   IRQ3,   IRQ4,   IRQ3 };

// This is the one and only constructor for an object of class
// PC8250.  A quick look at PC8250.H will show you that all of
// the parameters in the list except the port have default
// values, so the list isn't as overwhelming as it might look.

PC8250::PC8250( 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,
                Handler *handler,
                int uart_address,
                irq_name irq_line )
{
    int mcr;

    interrupt_handler = handler;
    port_name = port;
    error_status = RS232_SUCCESS;

// This section of code initializes most of the items in the
// isr_data structure, which contains all of the items used in
// the ISR.

    isr_data = new isr_data_block;
    if ( isr_data == 0 ) {
        error_status = RS232_MEMORY_ALLOCATION_ERROR;
        return;
    }
    set_uart_address_and_irq( handler, uart_address, irq_line );
    if ( error_status < RS232_SUCCESS )
        return;
    isr_data->overflow = 0;
    isr_data->tx_running = 0;
    isr_data->tx_int_count = 0;
    isr_data->rx_int_count = 0;
    isr_data->ls_int_count = 0;
    isr_data->ms_int_count = 0;
    isr_data->line_status = 0;
    isr_data->handshaking = 0;
    isr_data->blocking = 0;
    isr_data->blocked = 0;
    isr_data->send_handshake_char = -1;

// PC8250 has to share the debug output with the parent
// class.  To determine where our first line starts, we call the
// FormatDebugOutput() function from our parent class.

    first_debug_output_line = RS232::FormatDebugOutput();
    debug_line_count = FormatDebugOutput();

// Determine whether the UART is there and what type it is.

    check_uart();
    if ( error_status < RS232_SUCCESS )
        return;

//  Save all of the old UART settings, and then set it to the
//  new ones passed to the constructor.

    read_settings();
    saved_settings = settings;
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     dtr,
                     rts,
                     xon_xoff,
                     rts_cts,
                     dtr_dsr );
    write_settings();

// Here we set up the interrupt handler, then turn on
// interrupts.  After this code is done the UART will be
// running.

    outp( isr_data->uart + INTERRUPT_ENABLE_REGISTER, 0 );
    mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
    mcr |= MCR_OUT2;
    mcr &= ~MCR_LOOPBACK;
    outp( isr_data->uart + MODEM_CONTROL_REGISTER, mcr );
    if ( interrupt_handler == 0 ) {
        error_status = ConnectToIrq( irq,
                                     isr_data, (void (*)(void *))
                                     isr_8250 );
        if ( error_status < RS232_SUCCESS ) {
            outp( isr_data->uart + MODEM_CONTROL_REGISTER, 0 );
            outp( isr_data->uart + INTERRUPT_ENABLE_REGISTER, 0 );
            return;
        }
    } else {
        error_status = interrupt_handler->AddPort( port_name,
                                                   isr_data );
        if ( error_status < RS232_SUCCESS )
            return;
    }
    inp( isr_data->uart );  // Clear any pending interrupts
    inp( isr_data->uart + INTERRUPT_ID_REGISTER );
    _disable();
    isr_data->modem_status =
        (unsigned int)
            inp( isr_data->uart + MODEM_STATUS_REGISTER );
    outp( isr_data->uart + INTERRUPT_ENABLE_REGISTER,
            IER_RX_DATA_READY + IER_TX_HOLDING_REGISTER_EMPTY +
            IER_MODEM_STATUS + IER_LINE_STATUS );
    outp( 0x20, 0xc0 + IRQ3 - 1 );
    _enable();

// Finally, set up the last few parameters and exit.

    Dtr( settings.Dtr );
    Rts( settings.Rts );
    XonXoffHandshaking( settings.XonXoff );
    RtsCtsHandshaking( settings.RtsCts );
    DtrDsrHandshaking( settings.DtrDsr );
}

void PC8250::set_uart_address_and_irq( Handler *handler,
                                      int uart_address,
                                      irq_name irq_line )
{

// If I have a handler or have a defined irq_line, I won't
// use the default IRQs.  If I have a uart_address, I won't
// use the default UART address.

    if ( handler == 0 && irq_line == ILLEGAL_IRQ ) {
        if ( port_name > COM4 )
            error_status = RS232_PORT_NOT_FOUND;
        else
            irq = IRQs[ port_name ];
    } else
        irq = irq_line;

    if ( uart_address == 0 ) {
        if ( port_name > COM4 )
            error_status = RS232_PORT_NOT_FOUND;
        else
            isr_data->uart = Uarts[ port_name ];
    } else
        isr_data->uart = uart_address;
}

// The destructor has a much easier time of it than the
// constructor.  It disables interrupts, then restores the line
// settings of the UART.

PC8250::~PC8250( void )
{
    if ( error_status == RS232_SUCCESS ) {
        outp( isr_data->uart + INTERRUPT_ENABLE_REGISTER, 0 );
        outp( isr_data->uart + MODEM_CONTROL_REGISTER, 0 );
        if ( interrupt_handler == 0 )
            DisconnectFromIRQ( irq );
        else
            interrupt_handler->DeletePort( port_name );
        settings = saved_settings;
        write_settings();
        Dtr( settings.Dtr );
        Rts( settings.Rts );
    }
    if ( isr_data != 0 )
        delete isr_data;
}

// This routine determines if a UART is present, and if so,
// whether or not it is a 16550.  If it is a 16550, the FIFO is
// enabled with a trigger at 14 bytes.

void PC8250::check_uart( void )
{
    int temp;

    outp( isr_data->uart + FIFO_CONTROL_REGISTER, 0 );
    temp = inp( isr_data->uart + INTERRUPT_ID_REGISTER );
    if ( ( temp & 0xf8 ) != 0 ) {
        isr_data->uart_type = UART_UNKNOWN;
        error_status = RS232_PORT_NOT_FOUND;
        return;
    }
    outp( isr_data->uart + FIFO_CONTROL_REGISTER,
            FCR_FIFO_ENABLE + FCR_TRIGGER_14 );
    temp = inp( isr_data->uart + INTERRUPT_ID_REGISTER );
    if ( ( temp & 0xf8 ) == 0xc0 ) {
        isr_data->uart_type = UART_16550;
        fifo_setting = 14;
    } else {
        isr_data->uart_type = UART_8250;
        fifo_setting = 0;
        outp( isr_data->uart + FIFO_CONTROL_REGISTER, 0 );
    }
}

// After any function that reads data from the ISR buffers,
// this routine is called.  If the read operation dropped us
// below a handshaking trigger point, this routine will figure
// out what action to take.

void PC8250::check_rx_handshaking()
{
    int mcr;

// Take a quick exit if we aren't handshaking, blocking, or if
// the RX Queue is not below the low-water mark.

    if ( !isr_data->handshaking || !isr_data->blocking )
        return;
    if ( isr_data->RXQueue.InUseCount() > LowWaterMark )
        return;

// If RTS/CTS handshaking is in effect, I raise RTS.

    if ( ( isr_data->handshaking & rts_cts ) &&
         ( isr_data->blocking & rts_cts ) ) {
        _disable();
        mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
        mcr |= MCR_RTS;
        outp( isr_data->uart + MODEM_CONTROL_REGISTER, mcr );
        isr_data->blocking &= ~rts_cts;
        _enable();
    }

// If DTR/DSR handshaking is in effect, I raise DTR.

    if ( ( isr_data->handshaking & dtr_dsr ) &&
         ( isr_data->blocking & dtr_dsr ) ) {
        _disable();
        mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
        mcr |= MCR_DTR;
        outp( isr_data->uart + MODEM_CONTROL_REGISTER, mcr );
        isr_data->blocking &= ~dtr_dsr;
        _enable();
    }

// If XON/XOFF is in effect, I send an XON.  Note that if
// there is a pending XOFF that never made it out, I cancel it
// and don't send anything else.

    if ( ( isr_data->handshaking & xon_xoff ) &&
         ( isr_data->blocking & xon_xoff ) ) {
         _disable();
         isr_data->blocking &= ~xon_xoff;
         if ( isr_data->send_handshake_char == XOFF )
             isr_data->send_handshake_char = -1;
         else {
             isr_data->send_handshake_char = XON;
             jump_start( isr_data );
         }
         _enable();
    }
}

// This routine just pulls out a byte and checks for
// handshaking activity.

int PC8250::read_byte( void )
{
    int c;

    if ( error_status < 0 )
        return error_status;
    c = isr_data->RXQueue.Remove();
    if ( c < 0 )
        return RS232_TIMEOUT;
    check_rx_handshaking();
    return c;
}

// When sending a byte to the output buffer, I have to check
// to see if the TX interrupt system needs to be restarted.

int PC8250::write_byte( int c )
{
    if ( error_status < 0 )
        return error_status;
    if ( !isr_data->TXQueue.Insert( (unsigned char) c ) )
        return RS232_TIMEOUT;
    if ( !isr_data->tx_running && !isr_data->blocked )
        jump_start( isr_data );
    return RS232_SUCCESS;
}

// read_buffer() pulls in only as many bytes as are
// immediately available.  Any high-level functions such as
// timing out or looking for a terminator are handled by one of
// the higher level Read() routines from class RS232.

int PC8250::read_buffer( char *buffer, unsigned int count )
{
    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    while ( isr_data->RXQueue.InUseCount() ) {
        if ( count <= 0 )
            break;
        *buffer++ = (char) isr_data->RXQueue.Remove();
        count--;
        ByteCount++;
    }
    *buffer = '\0';
    if ( ByteCount > 0 )
        check_rx_handshaking();
    if ( count > 0 )
        return RS232_TIMEOUT;
    else
        return RS232_SUCCESS;
}

// write_buffer() sends as many characters as the buffer can
// immediately manage.  Like read_buffer(), it relies on higher
// level routines from class RS232 to perform the nicer functions
// such as adding termination, timing, etc.

int PC8250::write_buffer( char *buffer, unsigned int count )
{
    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    for ( ; ; ) {
        if ( count == 0 )
            break;
        if ( !isr_data->TXQueue.Insert( *buffer ) )
            break;
        buffer++;
        count--;
        ByteCount++;
    }
    if ( !isr_data->tx_running && !isr_data->blocked )
        jump_start( isr_data );
    if ( count > 0 )
        return RS232_TIMEOUT;
    else
        return RS232_SUCCESS;
}

// The Queue functions make it easy to flush the RX queue.
// After emptying it all, we need to be sure that handshaking
// gets managed.

int PC8250::FlushRXBuffer( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    _disable();
    isr_data->RXQueue.Clear();
    _enable();
    check_rx_handshaking();
    return RS232_SUCCESS;

}

// write_settings() is a protected routine called by the
// constructor and the public Set() function.   It is long and
// stringy, mostly because setting up the UART is just a long
// case of setting or clearing bits in control registers.  It
// might be possible to modularize this code, but it wouldn't be
// particularly useful.

RS232Error PC8250::write_settings( void )
{
    int lcr;
    int divisor_high;
    int divisor_low;
    RS232Error status = RS232_SUCCESS;
    long result_baud;

    if ( settings.BaudRate <= 0 || settings.BaudRate > 115200L ) {
        settings.BaudRate = 9600;
        status = RS232_ILLEGAL_BAUD_RATE;
    }
    divisor_low = (int) ( ( 115200L / settings.BaudRate ) & 0xff );
    divisor_high = (int) ( ( 115200L / settings.BaudRate ) >> 8 );
    result_baud = 115200L / ( 115200L / settings.BaudRate );
    if ( result_baud != settings.BaudRate ) {
        settings.BaudRate = result_baud;
        status = RS232_ILLEGAL_BAUD_RATE;
    }
    lcr = inp( isr_data->uart + LINE_CONTROL_REGISTER );
    lcr |= LCR_DLAB;
    _disable();
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    outp( isr_data->uart + DIVISOR_LATCH_LOW, divisor_low );
    outp( isr_data->uart + DIVISOR_LATCH_HIGH, divisor_high );
    lcr &= ~LCR_DLAB;
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    _enable();
    lcr &= ~LCR_PARITY_MASK;
    switch ( toupper( settings.Parity ) ) {
        case 'O' :
            lcr |= LCR_PARITY_ENABLE;
            break;
        case 'E' :
            lcr |= LCR_PARITY_ENABLE + LCR_EVEN_PARITY_SELECT;
            break;
        case 'M' :
            lcr |= LCR_PARITY_ENABLE + LCR_STICK_PARITY;
            break;
        case 'S' :
            lcr |= LCR_PARITY_ENABLE +
                   LCR_EVEN_PARITY_SELECT +
                   LCR_STICK_PARITY;
            break;
        default :
            settings.Parity = 'N';
            status = RS232_ILLEGAL_PARITY_SETTING;
        case 'N' :
            break;
    }
    lcr &= ~LCR_WORD_LENGTH_MASK;
    switch ( settings.WordLength ) {
        case 5 :
            break;
        case 6 :
            lcr |= LCR_WORD_LENGTH_SELECT_0;
            break;
        case 7 :
            lcr |= LCR_WORD_LENGTH_SELECT_1;
            break;
        default :
            settings.WordLength = 8;
            status = RS232_ILLEGAL_WORD_LENGTH;
        case 8 :
            lcr |= LCR_WORD_LENGTH_SELECT_0 +
                   LCR_WORD_LENGTH_SELECT_1;
            break;
    }
    lcr &= ~LCR_STOP_BITS;
    switch ( settings.StopBits ) {
        default :
            settings.StopBits = 1;
            status = RS232_ILLEGAL_STOP_BITS;
        case 1 :
            break;
        case 2 :
            lcr |= LCR_STOP_BITS;
            break;
    }
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    return status;
}

// read_settings() is the protected inverse of
// write_settings().  This routine just reads in the state of the
// UART into a settings object.  This is done when the routine
// starts up, so that the RS232 class will always have the saved
// settings available for restoration when the RS232 port is
// closed.

void PC8250::read_settings( void )
{
    int lcr;
    int mcr;
    int divisor_low;
    int divisor_high;

    lcr = inp( isr_data->uart + LINE_CONTROL_REGISTER );
    lcr |= LCR_DLAB;
    _disable();
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    divisor_low = inp( isr_data->uart + DIVISOR_LATCH_LOW );
    divisor_high = inp( isr_data->uart + DIVISOR_LATCH_HIGH );
    lcr &= ~LCR_DLAB;
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    _enable();

    if ( divisor_high | divisor_low )
        settings.BaudRate =
                115200L / ( ( divisor_high << 8 ) + divisor_low );
    else
        settings.BaudRate = -1;
    switch ( lcr & LCR_PARITY_MASK ) {
        case LCR_PARITY_ENABLE :
            settings.Parity = 'O';
            break;
        case LCR_PARITY_ENABLE + LCR_EVEN_PARITY_SELECT :
            settings.Parity = 'E';
            break;
        case LCR_PARITY_ENABLE + LCR_STICK_PARITY :
            settings.Parity = 'M';
            break;
        case LCR_PARITY_ENABLE +
             LCR_EVEN_PARITY_SELECT +
             LCR_STICK_PARITY :
            settings.Parity = 'S';
            break;
        default :
            settings.Parity = 'N';
            break;
    }
    switch ( lcr & LCR_WORD_LENGTH_MASK ) {
        case 0 :
            settings.WordLength = 5;
            break;
        case LCR_WORD_LENGTH_SELECT_0 :
            settings.WordLength = 6;
            break;
        case LCR_WORD_LENGTH_SELECT_1 :
            settings.WordLength = 7;
            break;
        case LCR_WORD_LENGTH_SELECT_0 + LCR_WORD_LENGTH_SELECT_1 :
            settings.WordLength = 8;
            break;
    }
    switch ( lcr & LCR_STOP_BITS ) {
        case 0 :
            settings.StopBits = 1;
            break;
        default :
            settings.StopBits = 2;
            break;
    }
    mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
    settings.Dtr = ( mcr & MCR_DTR ) != 0;
    settings.Rts = ( mcr & MCR_RTS ) != 0;
    settings.XonXoff = -1;
    settings.RtsCts = -1;
    settings.DtrDsr = -1;
}

// Set() takes advantage of code used by the constructor to
// set up some of the UART parameters.

RS232Error PC8250::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();
}

// This virtual routine is easily handled by a Queue member
// function.

int PC8250::TXSpaceFree( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return isr_data->TXQueue.FreeCount();
}

// The same thing is true here.

int PC8250::RXSpaceUsed( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return isr_data->RXQueue.InUseCount();
}

// The 8250 UART doesn't have an intelligent BREAK function,
// so we have to just sit on the line while the BREAK goes out.
// Hopefully the IdleFunction() can do something useful while
// this takes place.

int PC8250::Break( long milliseconds )
{
    int lcr;
    long timer;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    timer = ReadTime() + milliseconds;
    lcr = inp( isr_data->uart + LINE_CONTROL_REGISTER);
    lcr |= LCR_SET_BREAK;
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    while ( ReadTime() < timer )
        IdleFunction();
    lcr &= ~LCR_SET_BREAK;
    outp( isr_data->uart + LINE_CONTROL_REGISTER, lcr );
    return RS232_SUCCESS;
}

// The four modem status functions just check the bits that
// were read in the last time a modem status interrupt took
// place, and return them to the calling routine.

int PC8250::Cd( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( isr_data->modem_status & MSR_CD ) ? 1 : 0;
}

int PC8250::Ri( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( isr_data->modem_status & MSR_RI ) ? 1 : 0;
}

int PC8250::Cts( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( isr_data->modem_status & MSR_CTS ) ? 1 : 0;
}

int PC8250::Dsr( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( isr_data->modem_status & MSR_DSR ) ? 1 : 0;
}

// The four line status routines are similar to the modem
// status routines in that they just check a bit in a data
// member.  However, they also have an optional parameter that
// can be used to clear the error flag.  This is just a matter of
// clearing the same bit.

int PC8250::ParityError( int reset )
{
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    return_value =
           ( isr_data->line_status & LSR_PARITY_ERROR ) ? 1 : 0;
    if ( reset != UNCHANGED && reset != 0 ) {
        _disable();
        isr_data->line_status &= ~LSR_PARITY_ERROR;
        _enable();
    }
    return return_value;
}

int PC8250::BreakDetect( int reset )
{
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    return_value =
        ( isr_data->line_status & LSR_BREAK_DETECT ) ? 1 : 0;
    if ( reset != UNCHANGED && reset != 0 ) {
        _disable();
        isr_data->line_status &= ~LSR_BREAK_DETECT;
        _enable();
    }
    return return_value;
}

int PC8250::FramingError( int reset )
{
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    return_value =
          ( isr_data->line_status & LSR_FRAMING_ERROR ) ? 1 : 0;
    if ( reset != UNCHANGED && reset != 0 ) {
        _disable();
        isr_data->line_status &= ~LSR_FRAMING_ERROR;
        _enable();
    }
    return return_value;
}

int PC8250::HardwareOverrunError( int reset )
{
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    return_value =
          ( isr_data->line_status & LSR_OVERRUN_ERROR ) ? 1 : 0;
    if ( reset != UNCHANGED && reset != 0 ) {
        _disable();
        isr_data->line_status &= ~LSR_OVERRUN_ERROR;
        _enable();
    }
    return return_value;
}

// This just reads in the status bit from the isr_data
// structure, and optionally clears it.

int PC8250::SoftwareOverrunError( int clear )
{
    int temp = isr_data->overflow;
    if ( clear ) {
        _disable();
        isr_data->overflow = 0;
        _enable();
    }
    return temp;
}

// The three handshaking functions all have approximately the
// same mode of operation.  If the setting parameter is set to
// UNCHANGED, they just return a boolean indicating whether or
// not handshaking is in effect.  If handshaking is being turned
// on or off, things become a little more complicated.  The
// major complication is that after setting the bits needed by
// the ISR to handshake, they also have to take action to make
// sure the control lines and XON/XOFF output are where they need
// to be to accurately get things started.

int PC8250::XonXoffHandshaking( int setting )
{
    if ( setting != UNCHANGED ) {
        if ( setting )
            isr_data->handshaking |= xon_xoff;
        else {
            isr_data->handshaking &= ~xon_xoff;
            isr_data->blocked &= ~xon_xoff;
// If blocking, I need to send an XON
            if ( isr_data->blocking & xon_xoff ) {
                _disable();
                if ( isr_data->send_handshake_char == -1 )
                    isr_data->send_handshake_char = XON;
                else
                    isr_data->send_handshake_char = -1;
                _enable();
            }
         // Restart TX if I was blocked, or have to send and XON
            jump_start( isr_data );
            isr_data->blocking &= ~xon_xoff;
        }
        settings.XonXoff = ( setting != 0 );
    }
    return( ( isr_data->handshaking & xon_xoff ) != 0 );
}

int PC8250::RtsCtsHandshaking( int setting )
{
    int old_setting;

    if ( setting != UNCHANGED ) {
        old_setting = isr_data->handshaking & rts_cts;
        isr_data->handshaking &= ~rts_cts;
        isr_data->blocking &= ~rts_cts;
        isr_data->blocked &= ~rts_cts;
        if ( setting ) {
            Rts( 1 );
            _disable();
            if ( ( isr_data->modem_status & MSR_CTS ) == 0 )
                isr_data->blocked |= rts_cts;
            isr_data->handshaking |= rts_cts;
            _enable();
            settings.Rts = REMOTE_CONTROL;
        } else {
            if ( old_setting )
                Rts( 1 ); //If handshaking to go off, set RTS high
            if ( isr_data->blocked == 0 )
                jump_start( isr_data );
        }
        settings.RtsCts = ( setting != 0 );
    }
    return( ( isr_data->handshaking & rts_cts ) != 0 );
}

int PC8250::DtrDsrHandshaking( int setting )
{
    int old_setting;

    if ( setting != UNCHANGED ) {
        old_setting = isr_data->handshaking & dtr_dsr;
        isr_data->handshaking &= ~dtr_dsr;
        isr_data->blocking &= ~dtr_dsr;
        isr_data->blocked &= ~dtr_dsr;
        if ( setting ) {
            Dtr( 1 );
            _disable();
            if ( ( isr_data->modem_status & MSR_DSR ) == 0 )
                isr_data->blocked |= dtr_dsr;
            isr_data->handshaking |= dtr_dsr;
            _enable();
            settings.Dtr = REMOTE_CONTROL;
        } else {
            if ( old_setting )
                Dtr( 1 ); //If handshaking to go off, set RTS high
            if ( isr_data->blocked == 0 )
                jump_start( isr_data );
        }
        settings.DtrDsr = ( setting != 0 );
    }
    return( ( isr_data->handshaking & dtr_dsr ) != 0 );
}

// Just reading the state of the control line is relatively
// easy.  The setting returned is just the stored value in the
// settings element.  However, both of the next two routines have
// to handle setting or clearing the line as well.  This only
// gets complicated if handshaking is turned on.  If it is, these
// routines refuse to play with the control lines.

int PC8250::Dtr( int setting )
{
    int mcr;

    if ( setting != UNCHANGED ) {
        if ( isr_data->handshaking & dtr_dsr )
            return PC8250_HANDSHAKE_LINE_IN_USE;
        else {
            settings.Dtr = setting;
            _disable();
            mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
            if ( setting )
                mcr |= MCR_DTR;
            else
                mcr &= ~MCR_DTR;
            outp( isr_data->uart + MODEM_CONTROL_REGISTER, mcr );
            _enable();
        }
    }
    return settings.Dtr;
}

int PC8250::Rts( int setting )
{
    int mcr;

    if ( setting != UNCHANGED ) {
        if ( isr_data->handshaking & rts_cts )
            return PC8250_HANDSHAKE_LINE_IN_USE;
        else {
            settings.Rts = setting;
            _disable();
            mcr = inp( isr_data->uart + MODEM_CONTROL_REGISTER );
            if ( setting )
                mcr |= MCR_RTS;
            else
                mcr &= ~MCR_RTS;
            outp( isr_data->uart + MODEM_CONTROL_REGISTER, mcr );
            _enable();
        }
    }
    return settings.Rts;
}

// PeekBuffer uses the class Queue function to read as many
// bytes as possible from the RXBuffer.

int PC8250::PeekBuffer( void *buffer, unsigned int count )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    ByteCount =
          isr_data->RXQueue.Peek( (unsigned char *) buffer, count );
    ( (char *) buffer )[ ByteCount ] = '\0';
    return RS232_SUCCESS;
}

// The next two functions just return a count using a Queue
// class primitive function.

int PC8250::RXSpaceFree( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return isr_data->RXQueue.FreeCount();
}

int PC8250::TXSpaceUsed( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return isr_data->TXQueue.InUseCount();
}

// Flushing the TX buffer is easy when using the Queue class
// primitive.

int PC8250::FlushTXBuffer( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    _disable();
    isr_data->TXQueue.Clear();
    _enable();
    return RS232_SUCCESS;
}

// The debug output routine has three possible modes.  If the
// buffer passed to it is a null, it means it should just return
// the total number of lines used by the the output, which is 6
// plus the number used by the base class.  If the line number
// requested is less than where we start, the request is passed
// up the line to the base class.  Finally, if it is one of our
// lines, the buffer is formatted and returned to the calling
// routine.

int PC8250::FormatDebugOutput( char *buffer, int line_number )
{
    if ( buffer == 0 )
        return( first_debug_output_line +  6 );
    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: PC8250   "
                     "UART: %04x  "
                     "Overflow: %1d  "
                     "TX Running: %1d  "
                     "Line Status: %02x",
                     isr_data->uart,
                     ( isr_data->overflow ) ? 1 : 0,
                     ( isr_data->tx_running ) ? 1 : 0,
                     isr_data->line_status );
            break;
        case 1 :
            sprintf( buffer,
                     "TX Head, Tail, Count %4d %4d %4d  "
                     "RX Head,Tail,Count %4d %4d %4d",
                     isr_data->TXQueue.Head(),
                     isr_data->TXQueue.Tail(),
                     isr_data->TXQueue.InUseCount(),
                     isr_data->RXQueue.Head(),
                     isr_data->RXQueue.Tail(),
                     isr_data->RXQueue.InUseCount() );
            break;
        case 2 :
          sprintf( buffer,
                   "Counts: TX: %5u  RX: %5u  MS: %5u  LS: %5u  "
                   "CTS/DSR/RI/CD: %d%d%d%d",
                   isr_data->tx_int_count,
                   isr_data->rx_int_count,
                   isr_data->ms_int_count,
                   isr_data->ls_int_count,
                   ( isr_data->modem_status & MSR_CTS ) ? 1 : 0,
                   ( isr_data->modem_status & MSR_DSR ) ? 1 : 0,
                   ( isr_data->modem_status & MSR_RI ) ? 1 : 0,
                   ( isr_data->modem_status & MSR_CD ) ? 1 : 0 );
          break;
        case 3 :
            sprintf( buffer,
                     "Handshake DTR/RTS/XON : %d%d%d  "
                     "Blocking: %d%d%d  "
                     "Blocked: %d%d%d  "
                     "Handshake char: %04x",
                     ( isr_data->handshaking & dtr_dsr ) ? 1 : 0,
                     ( isr_data->handshaking & rts_cts ) ? 1 : 0,
                     ( isr_data->handshaking & xon_xoff ) ? 1 : 0,
                     ( isr_data->blocking & dtr_dsr ) ? 1 : 0,
                     ( isr_data->blocking & rts_cts ) ? 1 : 0,
                     ( isr_data->blocking & xon_xoff ) ? 1 : 0,
                     ( isr_data->blocked & dtr_dsr ) ? 1 : 0,
                     ( isr_data->blocked & rts_cts ) ? 1 : 0,
                     ( isr_data->blocked & xon_xoff ) ? 1 : 0,
                     isr_data->send_handshake_char );
            break;
        case 4 :
          sprintf( buffer,
                   "Parity Err: %d  "
                   "Break Det: %d  "
                   "Overrun Err: %d  "
                   "Framing Err: %d  "
                   "FIFO Setting: %2d",
                   ( isr_data->line_status & LSR_PARITY_ERROR )
                      ? 1 : 0,
                   ( isr_data->line_status & LSR_BREAK_DETECT )
                      ? 1 : 0,
                   ( isr_data->line_status & LSR_OVERRUN_ERROR )
                      ? 1 : 0,
                   ( isr_data->line_status & LSR_FRAMING_ERROR )
                      ? 1 : 0,
                   fifo_setting );
          break;
        case 5 :
            char *uart_name;
            switch( isr_data->uart_type ) {
                case UART_8250  : uart_name = "8250";    break;
                case UART_16550 : uart_name = "16550";   break;
                default         : uart_name = "Unknown"; break;
            }
            sprintf( buffer,
                     "Uart type: %-7s",
                     uart_name );
            break;
        default :
            return RS232_ILLEGAL_LINE_NUMBER;
    }
    return RS232_SUCCESS;
}

// Just like the debug format routine, ErrorName has to pass
// most requests up the line to the base class, saving only a few
// for it to respond to.

char * PC8250::ErrorName( int error )
{
    if ( error < RS232_NEXT_FREE_ERROR && error >= RS232_ERROR )
        return RS232::ErrorName( error );
    if (error < RS232_NEXT_FREE_WARNING && error >= RS232_WARNING)
        return RS232::ErrorName( error );
    if ( error >= RS232_SUCCESS )
        return RS232::ErrorName( error );
    switch ( error ) {
        case PC8250_UART_NOT_FOUND        :
                return( "UART not found" );
        case PC8250_HANDSHAKE_LINE_IN_USE :
                return( "Handshake line in use" );
        default                           :
                return( "Undefined error" );
    }
}
