home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 June
/
SIMTEL_0692.cdr
/
msdos
/
turbo_c
/
ibmcom_c.arc
/
IBMCOM.C
next >
Wrap
C/C++ Source or Header
|
1989-10-18
|
22KB
|
500 lines
/*****************************************************************************
* ibmcom.c *
*****************************************************************************
* DESCRIPTION: This file contains a set of routines for doing low-level *
* serial communications on the IBM PC. It was translated *
* directly from Wayne Conrad's IBMCOM.PAS version 3.1, with *
* the goal of near-perfect functional correspondence between *
* the Pascal and C versions. *
* *
* REVISIONS: 18 OCT 89 - RAC - Original translation from IBMCOM.PAS, with *
* liberal plagiarism of comments from the *
* Pascal. *
*****************************************************************************/
#include <stdio.h>
#include <dos.h>
#include "ibmcom.h"
/*****************************************************************************
* 8250 Definitions *
*****************************************************************************/
/* Offsets to various 8250 registers. Taken from IBM Technical */
/* Reference Manual, p. 1-225 */
#define TXBUFF 0 /* Transmit buffer register */
#define RXBUFF 0 /* Receive buffer register */
#define DLLSB 0 /* Divisor latch LS byte */
#define DLMSB 1 /* Divisor latch MS byte */
#define IER 1 /* Interrupt enable register */
#define IIR 2 /* Interrupt ID register */
#define LCR 3 /* Line control register */
#define MCR 4 /* Modem control register */
#define LSR 5 /* Line status register */
#define MSR 6 /* Modem status register */
/* Modem control register bits */
#define DTR 0x01 /* Data terminal ready */
#define RTS 0x02 /* Request to send */
#define OUT1 0x04 /* Output #1 */
#define OUT2 0x08 /* Output #2 */
#define LPBK 0x10 /* Loopback mode bit */
/* Modem status register bits */
#define DCTS 0x01 /* Delta clear to send */
#define DDSR 0x02 /* Delta data set ready */
#define TERI 0x04 /* Trailing edge ring indicator */
#define DRLSD 0x08 /* Delta Rx line signal detect */
#define CTS 0x10 /* Clear to send */
#define DSR 0x20 /* Data set ready */
#define RI 0x40 /* Ring indicator */
#define RLSD 0x80 /* Receive line signal detect */
/* Line control register bits */
#define DATA5 0x00 /* 5 Data bits */
#define DATA6 0x01 /* 6 Data bits */
#define DATA7 0x02 /* 7 Data bits */
#define DATA8 0x03 /* 8 Data bits */
#define STOP1 0x00 /* 1 Stop bit */
#define STOP2 0x04 /* 2 Stop bits */
#define NOPAR 0x00 /* No parity */
#define ODDPAR 0x08 /* Odd parity */
#define EVNPAR 0x18 /* Even parity */
#define STKPAR 0x28 /* Stick parity */
#define ZROPAR 0x38 /* Zero parity */
/* Line status register bits */
#define RDR 0x01 /* Receive data ready */
#define ERRS 0x1E /* All the error bits */
#define TXR 0x20 /* Transmitter ready */
/* Interrupt enable register bits */
#define DR 0x01 /* Data ready */
#define THRE 0x02 /* Tx buffer empty */
#define RLS 0x04 /* Receive line status */
/*****************************************************************************
* Names for Numbers *
*****************************************************************************/
#define MAX_PORT 4
#define TRUE 1
#define FALSE 0
/*****************************************************************************
* Global Data *
*****************************************************************************/
/* UART i/o addresses. Values depend upon which COMM port is selected */
int uart_data; /* Data register */
int uart_ier; /* Interrupt enable register */
int uart_iir; /* Interrupt identification register */
int uart_lcr; /* Line control register */
int uart_mcr; /* Modem control register */
int uart_lsr; /* Line status register */
int uart_msr; /* Modem status register */
char com_installed; /* Flag: Communications routines installed */
int intnum; /* Interrupt vector number for chosen port */
char i8259bit; /* 8259 bit mask */
char old_i8259_mask; /* Copy as it was when we were called */
char old_ier; /* Modem register contents saved for */
char old_mcr; /* restoring when we're done */
void interrupt (*old_vector)(); /* Place to save COM1 vector */
/* Transmit queue. Characters to be transmitted are held here until the */
/* UART is ready to transmit them. */
#define TX_QUEUE_SIZE 16 /* Transmit queue size. Change to suit */
char tx_queue[TX_QUEUE_SIZE];
int tx_in; /* Index of where to store next character */
int tx_out; /* Index of where to retrieve next character */
int tx_chars; /* Count of characters in queue */
/* Receive queue. Received characters are held here until retrieved by */
/* com_rx() */
#define RX_QUEUE_SIZE 4096 /* Receive queue size. Change to suit */
char rx_queue[RX_QUEUE_SIZE];
int rx_in; /* Index of where to store next character */
int rx_out; /* Index of where to retrieve next character */
int rx_chars; /* Count of characters in queue */
/*****************************************************************************
* com_install() *
*****************************************************************************
* DESCRIPTION: Installs the communications drivers. *
* *
* SYNOPSIS: status = com_install(int portnum); *
* int portnum; Desired port number *
* int status; 0 = Successful installation *
* 1 = Invalid port number *
* 2 = No UART for specified port *
* 3 = Drivers already installed *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from IBMCOM.PAS *
*****************************************************************************/
const int uart_base[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8 };
const char intnums[] = { 0x0C, 0x0B, 0x0C, 0x0B };
const char i8259levels[] = { 4, 3, 4, 3 };
int com_install(int portnum) {
if (com_installed) /* Drivers already installed */
return 3;
if ((portnum < 1) || (portnum > MAX_PORT)) /* Port number out of bounds */
return 1;
uart_data = uart_base[portnum-1]; /* Set UART I/O addresses */
uart_ier = uart_data + IER; /* for the selected comm */
uart_iir = uart_data + IIR; /* port */
uart_lcr = uart_data + LCR;
uart_mcr = uart_data + MCR;
uart_lsr = uart_data + LSR;
uart_msr = uart_data + MSR;
intnum = intnums[portnum-1]; /* Ditto for interrupt */
i8259bit = 1 << i8259levels[portnum-1]; /* vector and 8259 bit mask */
old_ier = inportb(uart_ier); /* Return an error if we */
outportb(uart_ier, 0); /* can't access the UART */
if (inportb(uart_ier) != 0)
return 2;
disable(); /* Save the original 8259 */
old_i8259_mask = inportb(0x21); /* mask, then disable the */
outportb(0x21, old_i8259_mask | i8259bit); /* 8259 for this interrupt */
enable();
com_flush_tx(); /* Clear the transmit and */
com_flush_rx(); /* receive queues */
old_vector = getvect(intnum); /* Save old COMM vector, */
setvect(intnum, &com_interrupt_driver); /* then install a new one, */
com_installed = TRUE; /* and note that we did */
outportb(uart_lcr, DATA8 + NOPAR + STOP1); /* 8 data, no parity, 1 stop */
disable(); /* Save MCR, then enable */
old_mcr = inportb(uart_mcr); /* interrupts onto the bus, */
outportb(uart_mcr, /* activate RTS and leave */
(old_mcr & DTR) | (OUT2 + RTS)); /* DTR the way it was */
enable();
outportb(uart_ier, DR); /* Enable receive interrupts */
disable(); /* Now enable the 8259 for */
outportb(0x21, inportb(0x21) & ~i8259bit); /* this interrupt */
enable();
return 0; /* Successful installation */
} /* End com_install() */
/*****************************************************************************
* com_install() *
*****************************************************************************
* DESCRIPTION: Denstalls the communications drivers completely, without *
* changing the baud rate or DTR. It tries to leave the *
* interrupt vectors and enables and everything else as they *
* were when the driver was installed. *
* *
* NOTE: This function MUST be called before returning to DOS, so the *
* interrupt vector won't point to our driver anymore, since it *
* will surely get overwritten by some other transient program *
* eventually. *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from IBMCOM.PAS *
*****************************************************************************/
void com_deinstall(void) {
if (com_installed) { /* Don't de-install twice! */
outportb(uart_mcr, old_mcr); /* Restore the UART */
outportb(uart_ier, old_ier); /* registers ... */
disable();
outportb(0x21, /* ... the 8259 interrupt */
(inportb(0x21) & ~i8259bit) | /* mask ... */
(old_i8259_mask & i8259bit));
enable();
setvect(intnum, old_vector); /* ... and the comm */
com_installed = FALSE; /* interrupt vector */
} /* End com_installed */
} /* End com_deinstall() */
/*****************************************************************************
* com_set_speed() *
*****************************************************************************
* DESCRIPTION: Sets the baud rate. *
* *
* SYNOPSIS: void com_set_speed(unsigned speed); *
* unsigned speed; Desired baud rate *
* *
* NOTES: The input parameter can be anything between 2 and 65535. *
* However, I (Wayne) am not sure that extremely high speeds *
* (those above 19200) will always work, since the baud rate *
* divisor will be six or less, where a difference of one can *
* represent a difference in baud rate of 3840 bits per second *
* or more.) *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from IBMCOM.PAS *
*****************************************************************************/
void com_set_speed(unsigned speed) {
unsigned divisor; /* A local temp */
if (com_installed) {
if (speed < 2) speed = 2; /* Force proper input */
divisor = 115200L / speed; /* Recond baud rate divisor */
disable(); /* Interrupts off */
outportb(uart_lcr, /* Set up to load baud rate */
inportb(uart_lcr) | 0x80); /* divisor into UART */
outport(uart_data, divisor); /* Do so */
outportb(uart_lcr, /* Back to normal UART ops */
inportb(uart_lcr) & ~0x80);
enable(); /* Interrupts back on */
} /* End "comm installed" */
} /* End com_set_speed() */
/*****************************************************************************
* com_set_parity() *
*****************************************************************************
* DESCRIPTION: Sets the parity and stop bits. *
* *
* SYNOPSIS: void com_set_parity(enum par_code parity, int stop_bits); *
* int code; COM_NONE = 8 data bits, no parity *
* COM_EVEN = 7 data, even parity *
* COM_ODD = 7 data, odd parity *
* COM_ZERO = 7 data, parity bit = zero *
* COM_ONE = 7 data, parity bit = one *
* int stop_bits; Must be 1 or 2 *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal *
*****************************************************************************/
const char lcr_vals[] = {
DATA8 + NOPAR,
DATA7 + EVNPAR,
DATA7 + ODDPAR,
DATA7 + STKPAR,
DATA7 + ZROPAR
} ;
void com_set_parity(enum par_code parity, int stop_bits) {
disable();
outportb(uart_lcr, lcr_vals[parity] | ((stop_bits == 2) ? STOP2 : STOP1));
enable();
} /* End com_set_parity() */
/*****************************************************************************
* com_raise_dtr() *
* com_lower_dtr() *
*****************************************************************************
* DESCRIPTION: These routines raise and lower the DTR line. Lowering DTR *
* causes most modems to hang up. *
* *
* REVISIONS: 18 OCT 89 - RAC - Transltated from the Pascal. *
*****************************************************************************/
void com_lower_dtr(void) {
if (com_installed) {
disable();
outportb(uart_mcr, inportb(uart_mcr) & ~DTR);
enable();
} /* End 'comm installed' */
} /* End com_raise_dtr() */
void com_raise_dtr(void) {
if (com_installed) {
disable();
outportb(uart_mcr, inportb(uart_mcr) | DTR);
enable();
} /* End 'comm installed' */
} /* End com_lower_dtr() */
/*****************************************************************************
* com_tx() *
* com_tx_string() *
*****************************************************************************
* DESCRIPTION: Transmit routines. com_tx() sends a single character by *
* waiting until the transmit buffer isn't full, then putting *
* the character into it. The interrupt driver will then send *
* the character once it is at the head of the transmit queue *
* and a transmit interrupt occurs. com_tx_string() sends a *
* string by repeatedly calling com_tx(). *
* *
* SYNOPSES: void com_tx(char c); Send the character c *
* void com_tx_string(char *s); Send the string s *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal *
*****************************************************************************/
void com_tx(char c) {
if (com_installed) {
while (!com_tx_ready()) ; /* Wait for non-full buffer */
disable(); /* Interrupts off */
tx_queue[tx_in++] = c; /* Stuff character in queue */
if (tx_in == TX_QUEUE_SIZE) tx_in = 0; /* Wrap index if needed */
tx_chars++; /* Number of char's in queue */
outportb(uart_ier, /* Enable UART tx interrupt */
inportb(uart_ier) | THRE);
enable(); /* Interrupts back on */
} /* End 'comm installed' */
} /* End com_tx() */
void com_tx_string(char *s) {
while (*s) com_tx(*s++); /* Send the string! */
} /* End com_tx_string() */
/*****************************************************************************
* com_rx() *
*****************************************************************************
* DESCRIPTION: Returns the next character from the receive buffer, or a *
* NULL character ('\0') if the buffer is empty. *
* *
* SYNOPSIS: c = com_rx(); *
* char c; The returned character *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal. *
*****************************************************************************/
char com_rx(void) {
char rv; /* Local temp */
if (!rx_chars || !com_installed) /* Return NULL if receive */
return '\0'; /* buffer is empty */
disable(); /* Interrupts off */
rv = rx_queue[rx_out++]; /* Grab char from queue */
if (rx_out == RX_QUEUE_SIZE) /* Wrap index if needed */
rx_out = 0;
rx_chars--; /* One less char in queue */
enable(); /* Interrupts back on */
return rv; /* The answer! */
} /* End com_rx() */
/*****************************************************************************
* Queue Status Routines *
*****************************************************************************
* DESCRIPTION: Small routines to return status of the transmit and receive *
* queues. *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal. *
*****************************************************************************/
int com_tx_ready(void) { /* Return TRUE if the */
return ((tx_chars < TX_QUEUE_SIZE) || /* transmit queue can */
(!com_installed)); /* accept a character */
} /* End com_tx_ready() */
int com_tx_empty(void) { /* Return TRUE if the */
return (!tx_chars || (!com_installed)); /* transmit queue is empty */
} /* End com_tx_empty() */
int com_rx_empty(void) { /* Return TRUE if the */
return (!rx_chars || (!com_installed)); /* receive queue is empty */
} /* End com_tx_empty() */
/*****************************************************************************
* com_flush_tx() *
* com_flush_rx() *
*****************************************************************************
* DESCRIPTION: Buffer flushers! These guys just initialize the transmit *
* and receive queues (respectively) to their empty state. *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal *
*****************************************************************************/
void com_flush_tx() { disable(); tx_chars = tx_in = tx_out = 0; enable(); }
void com_flush_rx() { disable(); rx_chars = rx_in = rx_out = 0; enable(); }
/*****************************************************************************
* com_carrier() *
*****************************************************************************
* DESCRIPTION: Returns TRUE if a carrier is present. *
* *
* REVISIONS: 18 OCT 89 - RAC - Translated from the Pascal. *
*****************************************************************************/
int com_carrier(void) {
return com_installed && (inportb(uart_msr) & RLSD);
} /* End com_carrier() */
/*****************************************************************************
* com_interrupt_driver() *
*****************************************************************************
* DESCRIPTION: Handles communications interrupts. The UART will interrupt *
* whenever a character has been received or when it is ready *
* to transmit another character. This routine responds by *
* sticking received characters into the receive queue and *
* yanking characters to be transmitted from the transmit queue *
* *
* REVISIOSN: 18 OCT 89 - RAC - Translated from the Pascal. *
*****************************************************************************/
void interrupt com_interrupt_driver() {
char iir; /* Local copy if IIR */
char c; /* Local character variable */
/* While bit 0 of the IIR is 0, there remains an interrupt to process */
while (!((iir = inportb(uart_iir)) & 1)) { /* While there is an int ... */
switch (iir) { /* Branch on interrupt type */
case 0: /* Modem status interrupt */
inportb(uart_msr); /* Just clear the interrupt */
break;
case 2: /* Transmit register empty */
/*****************************************************************************
* NOTE: The test of the line status register is to see if the transmit *
* holding register is truly empty. Some UARTS seem to cause *
* transmit interrupts when the holding register isn't empty, *
* causing transmitted characters to be lost. *
*****************************************************************************/
if (tx_chars <= 0) /* If tx buffer empty, turn */
outportb(uart_ier, /* off transmit interrupts */
inportb(uart_ier) & ~2);
else { /* Tx buffer not empty */
if (inportb(uart_lsr) & TXR) {
outportb(uart_data, tx_queue[tx_out++]);
if (tx_out == TX_QUEUE_SIZE)
tx_out = 0;
tx_chars--;
}
} /* End 'tx buffer not empty */
break;
case 4: /* Received data interrupt */
c = inportb(uart_data); /* Grab received character */
if (rx_chars < RX_QUEUE_SIZE) { /* If queue not full, save */
rx_queue[rx_in++] = c; /* the new character */
if (rx_in == RX_QUEUE_SIZE) /* Wrap index if needed */
rx_in = 0;
rx_chars++; /* Count the new character */
} /* End queue not full */
break;
case 6: /* Line status interrupt */
inportb(uart_lsr); /* Just clear the interrupt */
break;
} /* End switch */
} /* End 'is an interrupt' */
outportb(0x20, 0x20); /* Send EOI to 8259 */
} /* End com_interrupt_driver() */