home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / listings / v_01_01 / 1n01059a < prev    next >
Text File  |  1990-05-17  |  11KB  |  306 lines

  1. *****Listing 3*****
  2.  
  3. /*
  4.  *  COM.C
  5.  *
  6.  *  Copyright (C) Mark R. Nelson 1990
  7.  *
  8.  * This file contains all the code to implement a complete
  9.  * interrupt driven interface to one of the RS-232 COM
  10.  * ports on an IBM compatible PC.
  11.  */
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <dos.h>
  15. #include <conio.h>
  16. #include "com.h"
  17. /*
  18.  * This group of defines creates all the definitions used
  19.  * to access registers and bit fields in the 8250 UART.
  20.  * While the names may not be the best, they are the ones
  21.  * used in the 8250 data sheets, so they are generally not
  22.  * changed.  Since the definitions are only used in COM.C,
  23.  * they are not included in the COM.H header file where
  24.  * they might normally be expected.
  25.  */
  26. #define RBR              0    /* Receive buffer register */
  27. #define THR              0    /* Transmit holding reg.   */
  28. #define IER              1    /* Interrupt Enable reg.   */
  29. #define IER_RX_DATA      1    /* Enable RX interrupt bit */
  30. #define IER_THRE         2    /* Enable TX interrupt bit */
  31. #define IIR              2    /* Interrupt ID register   */
  32. #define IIR_MODEM_STATUS 0    /* Modem stat. interrupt ID*/
  33. #define IIR_TRANSMIT     2    /* Transmit interrupt ID   */
  34. #define IIR_RECEIVE      4    /* Receive interrupt ID    */
  35. #define IIR_LINE_STATUS  6    /* Line stat. interrupt ID */
  36. #define LCR              3    /* Line control register   */
  37. #define LCR_DLAB         0x80 /* Divisor access bit      */
  38. #define LCR_EVEN_PARITY  0x8  /* Set parity 'E' bits     */
  39. #define LCR_ODD_PARITY   0x18 /* Set parity 'O' bits     */
  40. #define LCR_NO_PARITY    0    /* Set parity 'N' bits     */
  41. #define LCR_1_STOP_BIT   0    /* Bits to set 1 stop bit  */
  42. #define LCR_2_STOP_BITS  4    /* Bits to set 2 stop bits */
  43. #define LCR_5_DATA_BITS  0    /* Bits to set 5 data bits */
  44. #define LCR_6_DATA_BITS  1    /* Bits to set 6 data bits */
  45. #define LCR_7_DATA_BITS  2    /* Bits to set 7 data bits */
  46. #define LCR_8_DATA_BITS  3    /* Bits to set 8 data bits */
  47. #define MCR              4    /* Modem control register  */
  48. #define MCR_DTR          1    /* Bit to turn on DTR      */
  49. #define MCR_RTS          2    /* Bit to turn on RTS      */
  50. #define MCR_OUT1         4    /* Bit to turn on OUT1     */
  51. #define MCR_OUT2         8    /* Bit to turn on OUT2     */
  52. #define LSR              5    /* Line Status register    */
  53. #define MSR              6    /* Modem Status register   */
  54. #define DLL              0    /* Divisor latch LSB       */
  55. #define DLM              1    /* Divisor latch MSB       */
  56. /*
  57.  * Various constants used only in this program.
  58.  */
  59. #define INT_CONTROLLER   0x20  /* The address of the 8259*/
  60. #define EOI              0x20  /* The end of int command */
  61. #define BREAK_VECTOR     0x23  /* The CTRL-BREAK vector  */
  62.  
  63. /*
  64.  * These are two static variables used in COM.C.  com is
  65.  * the pointer to the port that will be serviced by the
  66.  * ISR.  The old break handler points to the CTRL-BREAK
  67.  * handler that was in place before the port was opened.
  68.  * It will be restored when the port is closed.
  69.  */
  70. static PORT *com = NULL;
  71. static void ( interrupt far * old_break_handler )();
  72.  
  73. /*
  74.  * This routine intercepts the CTRL-BREAK vector.  This
  75.  * prevents the program from terminating before having a
  76.  * chance to turn off COM interrupts.  This handler does
  77.  * nothing, but it could be used to set a flag indicating
  78.  * it is time to abort.
  79.  */
  80. void interrupt far break_handler()
  81. {
  82.  
  83. }
  84. /*
  85.  * This is the interrupt service routine for the COM port.
  86.  * It sits in a loop reading the interrrupt ID register, then
  87.  * servicing one of the four different types of interrupts.
  88.  * Note that we shouldn't even get Modem Status and Line
  89.  * interrupts in this implementation, but they are left
  90.  * in for later enhancements.
  91.  */
  92. static void interrupt far interrupt_service_routine()
  93. {
  94.   unsigned char c;
  95.  
  96.   enable();
  97.   for ( ; ; ) {
  98.     switch ( inportb( com->uart_base + IIR ) ) {
  99. /*
  100.  * If the present interrupt is due to a modem status line
  101.  * change, the MSR is read to clear the interrupt, but
  102.  * nothing else is done.
  103.  */
  104.       case IIR_MODEM_STATUS :
  105.         inportb( com->uart_base + MSR );
  106.         break;
  107. /*
  108.  * If the interrupt is due to the transmit holding register
  109.  * being ready for another character, I first check to see
  110.  * if any characters are left in the output buffer.  If
  111.  * not, Transmit interrupts are disabled.  Otherwise, the
  112.  * next character is extracted from the buffer and written
  113.  * to the UART.
  114.  */
  115.       case IIR_TRANSMIT :
  116.         if ( com->out.read_index == com->out.write_index )
  117.           outportb( com->uart_base + IER, IER_RX_DATA );
  118.         else {
  119.           c = com->out.buffer[ com->out.read_index++ ];
  120.           outportb( com->uart_base + THR, c );
  121.         }
  122.         break;
  123. /*
  124.  * When a new character comes in and generates an
  125.  * interrupt, it is read in.  If there is room in the input
  126.  * buffer, it is stored, otherwise it is discarded.
  127.  */
  128.       case IIR_RECEIVE :
  129.         c = (unsigned char) inportb( com->uart_base+RBR );
  130.         if ((com->in.write_index+1 ) != com->in.read_index)
  131.             com->in.buffer[ com->in.write_index++ ] =  c ;
  132.         break;
  133. /*
  134.  * All this code does is read the Line Status register, to
  135.  * clear the source of the interrupt.
  136.  */
  137.       case IIR_LINE_STATUS :
  138.         inportb( com->uart_base + LSR );
  139.         break;
  140. /*
  141.  * If there are no valid interrupts left to service, an EOI
  142.  * is written to the 8259 interrupt controller, and the
  143.  * routine exits.
  144.  */
  145.       default :
  146.         outportb( INT_CONTROLLER, EOI );
  147.         return;
  148.     }
  149.   }
  150. }
  151. /*
  152.  * This routine opens an RS-232 port up.  This means it
  153.  * allocates space for a PORT strcture, initializes the
  154.  * input and output buffers, stores the uart address and
  155.  * the interrupt number.  It then gets and stored the
  156.  * interrupt vector presently set up for the UART, then
  157.  * installs its own.  It also sets up a handler to
  158.  * intercept the CTRL-BREAK handler.  Finally, it tells the
  159.  * 8259 interrupt controller to begin accepting interrupts
  160.  * on the IRQ line used by this COM port.
  161.  */
  162. PORT *port_open( int address, int int_number )
  163. {
  164.   unsigned char temp;
  165.   PORT *port;
  166.  
  167.   if ((port = malloc( sizeof( PORT ))) == NULL)
  168.     return( NULL );
  169.   com = port;
  170.   port->in.write_index = port->in.read_index = 0;
  171.   port->out.write_index = port->out.read_index = 0;
  172.   port->uart_base = address;
  173.   port->irq_mask = (char) 1 << (int_number % 8 );
  174.   port->interrupt_number = int_number;
  175.   port->old_vector = getvect( int_number );
  176.   setvect( int_number, interrupt_service_routine );
  177.   old_break_handler = getvect( BREAK_VECTOR );
  178.   setvect( BREAK_VECTOR, break_handler );
  179.   temp = (char) inportb( INT_CONTROLLER + 1 );
  180.   outportb( INT_CONTROLLER + 1, ~port->irq_mask & temp );
  181.   return( port );
  182. }
  183. /*
  184.  * This routine establishes the operating parameters for a
  185.  * port after it has been opened.  This means it sets the
  186.  * baud rate, parity, number of data bits, and number of
  187.  * stop bits.  Interrupts are disabled before the routine
  188.  * starts changing registers, and are then reenabled after
  189.  * the changes are complete.
  190.  */
  191. void port_set( PORT *port,
  192.                long speed,
  193.                char parity,
  194.                int data,
  195.                int stopbits )
  196. {
  197.   unsigned char lcr_out;
  198.   unsigned char mcr_out;
  199.   unsigned char low_divisor;
  200.   unsigned char high_divisor;
  201. /*
  202.  * First disable all interrupts from the port.  I also read
  203.  * RBR just in case their is a char sitting there ready to
  204.  * generate an interupt.
  205.  */
  206.   outportb( port->uart_base + IER, 0 );
  207.   inportb( port->uart_base );
  208. /*
  209.  * Writing the baud rate means first enabling the divisor
  210.  * latch registers, then writing the 16 bit divisor int
  211.  * two steps, then disabling the divisor latch so the other
  212.  * registers can be accessed normally.
  213.  */
  214.   low_divisor = (char) (115200L / speed ) & 0xff;
  215.   high_divisor = (char) ((115200L /  speed ) >> 8);
  216.   outportb( port->uart_base + LCR, LCR_DLAB );
  217.   outportb( port->uart_base + DLL, low_divisor );
  218.   outportb( port->uart_base + DLM, high_divisor );
  219.   outportb( port->uart_base + LCR, 0 );
  220. /*
  221.  * Setting up the line control register establishes the
  222.  * parity, number of bits, and number of stop bits.
  223.  */
  224.   if ( parity== 'E' )
  225.     lcr_out = LCR_EVEN_PARITY;
  226.   else if ( parity == 'O' )
  227.     lcr_out = LCR_ODD_PARITY;
  228.   else
  229.     lcr_out = LCR_NO_PARITY;
  230.  
  231.   if ( stopbits == 2 )
  232.     lcr_out |= LCR_2_STOP_BITS;
  233.  
  234.   if ( data == 6 )
  235.     lcr_out |= LCR_6_DATA_BITS;
  236.   else if ( data == 7 )
  237.     lcr_out |= LCR_7_DATA_BITS;
  238.   else if ( data == 8 )
  239.     lcr_out |= LCR_8_DATA_BITS;
  240.  
  241.   outportb( port->uart_base + LCR, lcr_out );
  242. /*
  243.  * I turn on RTS and DTR, as well as OUT2.  OUT2 is needed
  244.  * to allow interrupts on PC compatible cards.
  245.  */
  246.   mcr_out = MCR_RTS | MCR_DTR | MCR_OUT2 ;
  247.   outportb( port->uart_base + MCR, mcr_out );
  248. /*
  249.  * Finally, restart receiver interrupts, and exit.
  250.  */
  251.   outportb( port->uart_base + IER, IER_RX_DATA );
  252. }
  253. /*
  254.  * In order to close the port, I first disable interrupts
  255.  * at the UART, then disable interrupts from the UART's IRQ
  256.  * line at the 8259 interrupt controller.  DTR, RTS, and
  257.  * OUT2 are all dropped. The UART's previous interrupt
  258.  * handler is restored, and the old break handler
  259.  * is restored.  Finally, the port data structure is freed,
  260.  * and things should be completely back to normal.
  261.  */
  262. void port_close( PORT *port )
  263. {
  264.   unsigned char temp;
  265.  
  266.   outportb( port->uart_base + IER, 0 );
  267.   temp = (unsigned char) inportb( INT_CONTROLLER + 1 );
  268.   outportb( INT_CONTROLLER + 1, port->irq_mask | temp );
  269.   setvect( port->interrupt_number, port->old_vector );
  270.   setvect( BREAK_VECTOR, old_break_handler );
  271.   outportb( port->uart_base + MCR, 0 );
  272.   free( port );
  273. }
  274. /*
  275.  * This routine is used to send a single character out to
  276.  * the UART.  If there is room in the output buffer, the
  277.  * character is inserted in the buffer.  Then the routine
  278.  * checks to see if Transmit interrupts are presently
  279.  * enabled for this UART.  If they aren't, they are turned
  280.  * on so the ISR will see this new character.
  281.  */
  282. int port_putc( unsigned char c, PORT *port )
  283. {
  284.   if (( port->out.write_index+1 ) == port->out.read_index)
  285.     return( -1 );
  286.   port->out.buffer[ port->out.write_index ] = c;
  287.   port->out.write_index += 1;
  288.   if (( inportb( port->uart_base + IER ) & IER_THRE) == 0 )
  289.     outportb( port->uart_base+IER,IER_THRE | IER_RX_DATA);
  290.   return( c );
  291. }
  292. /*
  293.  * This routine checks to see if there is a character
  294.  * available in the input buffer for the specified port.
  295.  * If there is, it is pulled out and returned to the
  296.  * caller.
  297.  */
  298. int port_getc(PORT *port)
  299. {
  300.   if ( port->in.write_index == port->in.read_index )
  301.     return( -1 );
  302.   else
  303.     return( port->in.buffer[ port->in.read_index++ ] );
  304. }
  305.  
  306.