home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 September / Simtel20_Sept92.cdr / msdos / turbo_c / ibmcom_c.arc / IBMCOM.C next >
C/C++ Source or Header  |  1989-10-18  |  22KB  |  500 lines

  1. /*****************************************************************************
  2.  *                   ibmcom.c                     *
  3.  *****************************************************************************
  4.  * DESCRIPTION:    This file contains a set of routines for doing low-level     *
  5.  *        serial communications on the IBM PC.  It was translated         *
  6.  *        directly from Wayne Conrad's IBMCOM.PAS version 3.1, with    *
  7.  *        the goal of near-perfect functional correspondence between   *
  8.  *        the Pascal and C versions.                     *
  9.  *                                         *
  10.  * REVISIONS:    18 OCT 89 - RAC - Original translation from IBMCOM.PAS, with *
  11.  *                  liberal plagiarism of comments from the    *
  12.  *                  Pascal.                     *
  13.  *****************************************************************************/
  14.  
  15. #include    <stdio.h>
  16. #include    <dos.h>
  17. #include    "ibmcom.h"
  18.  
  19. /*****************************************************************************
  20.  *                   8250 Definitions                     *
  21.  *****************************************************************************/
  22.  
  23. /*      Offsets to various 8250 registers.  Taken from IBM Technical         */
  24. /*      Reference Manual, p. 1-225                                           */
  25.  
  26. #define TXBUFF  0                       /* Transmit buffer register */
  27. #define RXBUFF  0                       /* Receive buffer register */
  28. #define DLLSB   0                       /* Divisor latch LS byte */
  29. #define DLMSB   1                       /* Divisor latch MS byte */
  30. #define IER     1                       /* Interrupt enable register */
  31. #define IIR     2                       /* Interrupt ID register */
  32. #define LCR     3                       /* Line control register */
  33. #define MCR     4                       /* Modem control register */
  34. #define LSR     5                       /* Line status register */
  35. #define MSR     6                       /* Modem status register */
  36.  
  37. /*      Modem control register bits                                          */
  38.  
  39. #define DTR     0x01                    /* Data terminal ready */
  40. #define RTS     0x02                    /* Request to send */
  41. #define OUT1    0x04                    /* Output #1 */
  42. #define OUT2    0x08                    /* Output #2 */
  43. #define LPBK    0x10                    /* Loopback mode bit */
  44.  
  45. /*      Modem status register bits                                           */
  46.  
  47. #define DCTS    0x01                    /* Delta clear to send */
  48. #define DDSR    0x02                    /* Delta data set ready */
  49. #define TERI    0x04                    /* Trailing edge ring indicator */
  50. #define DRLSD   0x08                    /* Delta Rx line signal detect */
  51. #define CTS     0x10                    /* Clear to send */
  52. #define DSR     0x20                    /* Data set ready */
  53. #define RI      0x40                    /* Ring indicator */
  54. #define RLSD    0x80                    /* Receive line signal detect */
  55.  
  56. /*      Line control register bits                                           */
  57.  
  58. #define DATA5   0x00                    /* 5 Data bits */
  59. #define DATA6   0x01                    /* 6 Data bits */
  60. #define DATA7   0x02                    /* 7 Data bits */
  61. #define DATA8   0x03                    /* 8 Data bits */
  62.  
  63. #define STOP1   0x00                    /* 1 Stop bit */
  64. #define STOP2   0x04                    /* 2 Stop bits */
  65.  
  66. #define NOPAR   0x00                    /* No parity */
  67. #define ODDPAR  0x08                    /* Odd parity */
  68. #define EVNPAR  0x18                    /* Even parity */
  69. #define STKPAR  0x28                    /* Stick parity */
  70. #define ZROPAR    0x38            /* Zero parity */
  71.  
  72. /*      Line status register bits                                            */
  73.  
  74. #define RDR     0x01                    /* Receive data ready */
  75. #define ERRS    0x1E                    /* All the error bits */
  76. #define TXR     0x20                    /* Transmitter ready */
  77.  
  78. /*      Interrupt enable register bits                                       */
  79.  
  80. #define DR      0x01                    /* Data ready */
  81. #define THRE    0x02                    /* Tx buffer empty */
  82. #define RLS     0x04                    /* Receive line status */
  83.  
  84. /*****************************************************************************
  85.  *                   Names for Numbers                 *
  86.  *****************************************************************************/
  87.  
  88. #define MAX_PORT    4
  89.  
  90. #define TRUE        1
  91. #define FALSE        0
  92.  
  93. /*****************************************************************************
  94.  *                  Global Data                     *
  95.  *****************************************************************************/
  96.  
  97. /*  UART i/o addresses.  Values depend upon which COMM port is selected  */
  98.  
  99. int    uart_data;        /* Data register */
  100. int    uart_ier;        /* Interrupt enable register */
  101. int    uart_iir;        /* Interrupt identification register */
  102. int    uart_lcr;        /* Line control register */
  103. int    uart_mcr;        /* Modem control register */
  104. int    uart_lsr;        /* Line status register */
  105. int    uart_msr;        /* Modem status register */
  106.  
  107. char    com_installed;        /* Flag: Communications routines installed */
  108. int    intnum;            /* Interrupt vector number for chosen port */
  109. char    i8259bit;        /* 8259 bit mask */
  110. char    old_i8259_mask;        /* Copy as it was when we were called */
  111. char    old_ier;        /* Modem register contents saved for */
  112. char    old_mcr;        /*  restoring when we're done */
  113. void interrupt (*old_vector)();    /* Place to save COM1 vector */
  114.  
  115. /*  Transmit queue.  Characters to be transmitted are held here until the  */
  116. /*  UART is ready to transmit them.  */
  117.  
  118. #define TX_QUEUE_SIZE    16    /* Transmit queue size.  Change to suit */
  119.  
  120. char    tx_queue[TX_QUEUE_SIZE];
  121. int    tx_in;            /* Index of where to store next character */
  122. int    tx_out;            /* Index of where to retrieve next character */
  123. int    tx_chars;        /* Count of characters in queue */
  124.  
  125. /*  Receive queue.  Received characters are held here until retrieved by  */
  126. /*  com_rx()  */
  127.  
  128. #define RX_QUEUE_SIZE    4096    /* Receive queue size.  Change to suit */
  129.  
  130. char    rx_queue[RX_QUEUE_SIZE];
  131. int    rx_in;            /* Index of where to store next character */
  132. int    rx_out;            /* Index of where to retrieve next character */
  133. int    rx_chars;        /* Count of characters in queue */
  134.  
  135. /*****************************************************************************
  136.  *                 com_install()                     *
  137.  *****************************************************************************
  138.  * DESCRIPTION:    Installs the communications drivers.                 *
  139.  *                                         *
  140.  * SYNOPSIS:    status = com_install(int portnum);                 *
  141.  *        int    portnum;    Desired port number             *
  142.  *        int    status;        0 = Successful installation         *
  143.  *                    1 = Invalid port number                *
  144.  *                    2 = No UART for specified port         *
  145.  *                    3 = Drivers already installed         *
  146.  *                                         *
  147.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  148.  *****************************************************************************/
  149.  
  150. const int    uart_base[] =    { 0x3F8, 0x2F8, 0x3E8, 0x2E8 };
  151. const char    intnums[] =    { 0x0C,  0x0B,  0x0C,  0x0B };
  152. const char    i8259levels[] =    { 4,     3,     4,     3 };
  153.  
  154. int com_install(int portnum) {
  155.  
  156.     if (com_installed)                /* Drivers already installed */
  157.     return 3;
  158.     if ((portnum < 1) || (portnum > MAX_PORT))    /* Port number out of bounds */
  159.     return 1;
  160.  
  161.     uart_data = uart_base[portnum-1];        /* Set UART I/O addresses */
  162.     uart_ier  = uart_data + IER;        /*  for the selected comm */
  163.     uart_iir  = uart_data + IIR;        /*  port */
  164.     uart_lcr  = uart_data + LCR;
  165.     uart_mcr  = uart_data + MCR;
  166.     uart_lsr  = uart_data + LSR;
  167.     uart_msr  = uart_data + MSR;
  168.     intnum    = intnums[portnum-1];        /* Ditto for interrupt */
  169.     i8259bit  = 1 << i8259levels[portnum-1];    /*  vector and 8259 bit mask */
  170.  
  171.     old_ier = inportb(uart_ier);        /* Return an error if we */
  172.     outportb(uart_ier, 0);            /*  can't access the UART */
  173.     if (inportb(uart_ier) != 0)
  174.     return 2;
  175.  
  176.     disable();                    /* Save the original 8259 */
  177.     old_i8259_mask = inportb(0x21);        /*  mask, then disable the */
  178.     outportb(0x21, old_i8259_mask | i8259bit);    /*  8259 for this interrupt */
  179.     enable();
  180.  
  181.     com_flush_tx();                /* Clear the transmit and */
  182.     com_flush_rx();                /*  receive queues */
  183.  
  184.     old_vector = getvect(intnum);        /* Save old COMM vector, */
  185.     setvect(intnum, &com_interrupt_driver);    /*  then install a new one, */
  186.     com_installed = TRUE;            /*  and note that we did */
  187.  
  188.     outportb(uart_lcr, DATA8 + NOPAR + STOP1);    /* 8 data, no parity, 1 stop */
  189.  
  190.     disable();                    /* Save MCR, then enable */
  191.     old_mcr = inportb(uart_mcr);        /*  interrupts onto the bus, */
  192.     outportb(uart_mcr,                /*  activate RTS and leave */
  193.          (old_mcr & DTR) | (OUT2 + RTS));    /*  DTR the way it was */
  194.     enable();
  195.  
  196.     outportb(uart_ier, DR);            /* Enable receive interrupts */
  197.  
  198.     disable();                    /* Now enable the 8259 for */
  199.     outportb(0x21, inportb(0x21) & ~i8259bit);    /*  this interrupt */
  200.     enable();
  201.     return 0;                    /* Successful installation */
  202.     }                        /* End com_install() */
  203.  
  204. /*****************************************************************************
  205.  *                 com_install()                     *
  206.  *****************************************************************************
  207.  * DESCRIPTION:    Denstalls the communications drivers completely, without     *
  208.  *        changing the baud rate or DTR.  It tries to leave the        *
  209.  *        interrupt vectors and enables and everything else as they    *
  210.  *        were when the driver was installed.                 *
  211.  *                                         *
  212.  * NOTE:    This function MUST be called before returning to DOS, so the *
  213.  *        interrupt vector won't point to our driver anymore, since it *
  214.  *        will surely get overwritten by some other transient program  *
  215.  *        eventually.                             *
  216.  *                                         *
  217.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  218.  *****************************************************************************/
  219.  
  220. void com_deinstall(void) {
  221.  
  222.     if (com_installed) {            /* Don't de-install twice! */
  223.     outportb(uart_mcr, old_mcr);        /* Restore the UART */
  224.     outportb(uart_ier, old_ier);        /*  registers ... */
  225.     disable();
  226.     outportb(0x21,                /*  ... the 8259 interrupt */
  227.          (inportb(0x21)  & ~i8259bit) | /*  mask ... */
  228.          (old_i8259_mask &  i8259bit));
  229.     enable();
  230.     setvect(intnum, old_vector);        /*  ... and the comm */
  231.     com_installed = FALSE;            /*  interrupt vector */
  232.     }                    /* End com_installed */
  233.     }                        /* End com_deinstall() */
  234.  
  235. /*****************************************************************************
  236.  *                com_set_speed()                     *
  237.  *****************************************************************************
  238.  * DESCRIPTION:    Sets the baud rate.                         *
  239.  *                                         *
  240.  * SYNOPSIS:    void com_set_speed(unsigned speed);                 *
  241.  *        unsigned speed;            Desired baud rate         *
  242.  *                                         *
  243.  * NOTES:    The input parameter can be anything between 2 and 65535.     *
  244.  *        However, I (Wayne) am not sure that extremely high speeds    *
  245.  *        (those above 19200) will always work, since the baud rate    *
  246.  *        divisor will be six or less, where a difference of one can   *
  247.  *        represent a difference in baud rate of 3840 bits per second  *
  248.  *        or more.)                             *
  249.  *                                         *
  250.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  251.  *****************************************************************************/
  252.  
  253. void com_set_speed(unsigned speed) {
  254.  
  255.     unsigned    divisor;            /* A local temp */
  256.  
  257.     if (com_installed) {
  258.     if (speed < 2) speed = 2;        /* Force proper input */
  259.     divisor = 115200L / speed;        /* Recond baud rate divisor */
  260.     disable();                /* Interrupts off */
  261.     outportb(uart_lcr,            /* Set up to load baud rate */
  262.          inportb(uart_lcr) | 0x80);    /*  divisor into UART */
  263.     outport(uart_data, divisor);        /* Do so */
  264.     outportb(uart_lcr,            /* Back to normal UART ops */
  265.          inportb(uart_lcr) & ~0x80);
  266.     enable();                /* Interrupts back on */
  267.     }                    /* End "comm installed" */
  268.     }                        /* End com_set_speed() */
  269.  
  270. /*****************************************************************************
  271.  *                   com_set_parity()                     *
  272.  *****************************************************************************
  273.  * DESCRIPTION: Sets the parity and stop bits.                     *
  274.  *                                         *
  275.  * SYNOPSIS:    void com_set_parity(enum par_code parity, int stop_bits);    *
  276.  *        int    code;        COM_NONE = 8 data bits, no parity    *
  277.  *                    COM_EVEN = 7 data, even parity         *
  278.  *                    COM_ODD  = 7 data, odd parity         *
  279.  *                    COM_ZERO = 7 data, parity bit = zero *
  280.  *                    COM_ONE  = 7 data, parity bit = one  *
  281.  *        int    stop_bits;    Must be 1 or 2                 *
  282.  *                                         *
  283.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  284.  *****************************************************************************/
  285.  
  286. const char    lcr_vals[] = {
  287.             DATA8 + NOPAR,
  288.             DATA7 + EVNPAR,
  289.             DATA7 + ODDPAR,
  290.             DATA7 + STKPAR,
  291.             DATA7 + ZROPAR
  292.             } ;
  293.  
  294. void com_set_parity(enum par_code parity, int stop_bits) {
  295.     disable();
  296.     outportb(uart_lcr, lcr_vals[parity] | ((stop_bits == 2) ? STOP2 : STOP1));
  297.     enable();    
  298.     }                        /* End com_set_parity() */
  299.  
  300. /*****************************************************************************
  301.  *                com_raise_dtr()                     *
  302.  *                com_lower_dtr()                     *
  303.  *****************************************************************************
  304.  * DESCRIPTION:    These routines raise and lower the DTR line.  Lowering DTR   *
  305.  *        causes most modems to hang up.                     *
  306.  *                                         *
  307.  * REVISIONS:    18 OCT 89 - RAC - Transltated from the Pascal.             *
  308.  *****************************************************************************/
  309.  
  310. void com_lower_dtr(void) {
  311.     if (com_installed) {
  312.         disable();
  313.     outportb(uart_mcr, inportb(uart_mcr) & ~DTR);
  314.         enable();
  315.     }                    /* End 'comm installed' */
  316.     }                        /* End com_raise_dtr() */
  317.  
  318. void com_raise_dtr(void) {
  319.     if (com_installed) {
  320.         disable();
  321.     outportb(uart_mcr, inportb(uart_mcr) | DTR);
  322.         enable();
  323.     }                    /* End 'comm installed' */
  324.     }                        /* End com_lower_dtr() */
  325.  
  326. /*****************************************************************************
  327.  *                   com_tx()                     *
  328.  *                com_tx_string()                     *
  329.  *****************************************************************************
  330.  * DESCRIPTION: Transmit routines.  com_tx() sends a single character by     *
  331.  *        waiting until the transmit buffer isn't full, then putting   *
  332.  *        the character into it.  The interrupt driver will then send  *
  333.  *        the character once it is at the head of the transmit queue   *
  334.  *        and a transmit interrupt occurs.  com_tx_string() sends a    *
  335.  *        string by repeatedly calling com_tx().                 *
  336.  *                                         *
  337.  * SYNOPSES:    void    com_tx(char c);        Send the character c         *
  338.  *        void    com_tx_string(char *s);    Send the string s         *
  339.  *                                         *
  340.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  341.  *****************************************************************************/
  342.  
  343. void com_tx(char c) {
  344.     if (com_installed) {
  345.     while (!com_tx_ready()) ;        /* Wait for non-full buffer */
  346.     disable();                /* Interrupts off */
  347.     tx_queue[tx_in++] = c;            /* Stuff character in queue */
  348.     if (tx_in == TX_QUEUE_SIZE) tx_in = 0;    /* Wrap index if needed */
  349.     tx_chars++;                /* Number of char's in queue */
  350.     outportb(uart_ier,            /* Enable UART tx interrupt */
  351.          inportb(uart_ier) | THRE);
  352.     enable();                /* Interrupts back on */
  353.     }                    /* End 'comm installed' */
  354.     }                        /* End com_tx() */
  355.  
  356. void com_tx_string(char *s) {
  357.     while (*s) com_tx(*s++);            /* Send the string! */
  358.     }                        /* End com_tx_string() */
  359.  
  360. /*****************************************************************************
  361.  *                   com_rx()                     *
  362.  *****************************************************************************
  363.  * DESCRIPTION:    Returns the next character from the receive buffer, or a     *
  364.  *        NULL character ('\0') if the buffer is empty.             *
  365.  *                                         *
  366.  * SYNOPSIS:    c = com_rx();                             *
  367.  *        char    c;            The returned character         *
  368.  *                                         *
  369.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  370.  *****************************************************************************/
  371.  
  372. char    com_rx(void) {
  373.  
  374.     char    rv;                /* Local temp */
  375.  
  376.     if (!rx_chars || !com_installed)        /* Return NULL if receive */
  377.     return '\0';                /*  buffer is empty */
  378.     disable();                    /* Interrupts off */
  379.     rv = rx_queue[rx_out++];            /* Grab char from queue */
  380.     if (rx_out == RX_QUEUE_SIZE)        /* Wrap index if needed */
  381.     rx_out = 0;
  382.     rx_chars--;                    /* One less char in queue */
  383.     enable();                    /* Interrupts back on */
  384.     return rv;                    /* The answer! */
  385.     }                        /* End com_rx() */
  386.  
  387. /*****************************************************************************
  388.  *                 Queue Status Routines                 *
  389.  *****************************************************************************
  390.  * DESCRIPTION:    Small routines to return status of the transmit and receive  *
  391.  *        queues.                                 *
  392.  *                                         *
  393.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  394.  *****************************************************************************/
  395.  
  396. int com_tx_ready(void) {            /* Return TRUE if the */
  397.     return ((tx_chars < TX_QUEUE_SIZE) ||    /*  transmit queue can */
  398.         (!com_installed));            /*  accept a character */
  399.     }                        /* End com_tx_ready() */
  400.  
  401. int com_tx_empty(void) {            /* Return TRUE if the */
  402.     return (!tx_chars || (!com_installed));    /*  transmit queue is empty */
  403.     }                        /* End com_tx_empty() */
  404.  
  405. int com_rx_empty(void) {            /* Return TRUE if the */
  406.     return (!rx_chars || (!com_installed));    /*  receive queue is empty */
  407.     }                        /* End com_tx_empty() */
  408.  
  409. /*****************************************************************************
  410.  *                com_flush_tx()                     *
  411.  *                com_flush_rx()                     *
  412.  *****************************************************************************
  413.  * DESCRIPTION:    Buffer flushers!  These guys just initialize the transmit    *
  414.  *        and receive queues (respectively) to their empty state.         *
  415.  *                                         *
  416.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  417.  *****************************************************************************/
  418.  
  419. void com_flush_tx() { disable(); tx_chars = tx_in = tx_out = 0; enable(); }
  420. void com_flush_rx() { disable(); rx_chars = rx_in = rx_out = 0; enable(); }
  421.  
  422. /*****************************************************************************
  423.  *                 com_carrier()                     *
  424.  *****************************************************************************
  425.  * DESCRIPTION:    Returns TRUE if a carrier is present.                 *
  426.  *                                         *
  427.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  428.  *****************************************************************************/
  429.  
  430. int com_carrier(void) {
  431.     return com_installed && (inportb(uart_msr) & RLSD);
  432.     }                        /* End com_carrier() */
  433.  
  434. /*****************************************************************************
  435.  *                com_interrupt_driver()                 *
  436.  *****************************************************************************
  437.  * DESCRIPTION:    Handles communications interrupts.  The UART will interrupt  *
  438.  *        whenever a character has been received or when it is ready   *
  439.  *        to transmit another character.  This routine responds by     *
  440.  *        sticking received characters into the receive queue and      *
  441.  *        yanking characters to be transmitted from the transmit queue *
  442.  *                                         *
  443.  * REVISIOSN:    18 OCT 89 - RAC - Translated from the Pascal.             *
  444.  *****************************************************************************/
  445.  
  446. void interrupt com_interrupt_driver() {
  447.  
  448.     char    iir;                /* Local copy if IIR */
  449.     char    c;                /* Local character variable */
  450.  
  451. /*  While bit 0 of the IIR is 0, there remains an interrupt to process  */
  452.  
  453.     while (!((iir = inportb(uart_iir)) & 1)) {    /* While there is an int ... */
  454.     switch (iir) {                /* Branch on interrupt type */
  455.  
  456.         case 0:                /* Modem status interrupt */
  457.         inportb(uart_msr);        /* Just clear the interrupt */
  458.         break;
  459.  
  460.         case 2:                /* Transmit register empty */
  461.  
  462. /*****************************************************************************
  463.  *  NOTE:  The test of the line status register is to see if the transmit    *
  464.  *       holding register is truly empty.  Some UARTS seem to cause         *
  465.  *       transmit interrupts when the holding register isn't empty,         *
  466.  *       causing transmitted characters to be lost.                 *
  467.  *****************************************************************************/
  468.  
  469.         if (tx_chars <= 0)        /* If tx buffer empty, turn */
  470.             outportb(uart_ier,        /*  off transmit interrupts */
  471.                  inportb(uart_ier) & ~2);
  472.         else {                /* Tx buffer not empty */
  473.             if (inportb(uart_lsr) & TXR) {
  474.             outportb(uart_data, tx_queue[tx_out++]);
  475.             if (tx_out == TX_QUEUE_SIZE)
  476.                 tx_out = 0;
  477.             tx_chars--;
  478.             }
  479.             }                /* End 'tx buffer not empty */
  480.         break;
  481.  
  482.         case 4:                /* Received data interrupt */
  483.         c = inportb(uart_data);        /* Grab received character */
  484.         if (rx_chars < RX_QUEUE_SIZE) { /* If queue not full, save */
  485.             rx_queue[rx_in++] = c;    /*  the new character */
  486.             if (rx_in == RX_QUEUE_SIZE) /* Wrap index if needed */
  487.             rx_in = 0;
  488.             rx_chars++;            /* Count the new character */
  489.             }                /* End queue not full */
  490.         break;
  491.  
  492.         case 6:                /* Line status interrupt */
  493.         inportb(uart_lsr);        /* Just clear the interrupt */
  494.         break;
  495.  
  496.         }                    /* End switch */
  497.     }                    /* End 'is an interrupt' */
  498.     outportb(0x20, 0x20);            /* Send EOI to 8259 */
  499.     }                        /* End com_interrupt_driver() */
  500.