// ******************** START OF BIOSPORT.CPP ******************** // // This file contains all of the code used by the BIOSPort and // EBIOSPort classes. // #include #include #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(); }