home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Virtual Reality Zone
/
VRZONE.ISO
/
mac
/
PC
/
REND386
/
JIREND
/
SERIAL.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-04-11
|
22KB
|
655 lines
// *********************************
// serial.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.
// 31 MAR 93 - J.Isdale - Modified to support multiple lines
// 1 APR 93 - J.Isdale - Modified to dynamically allocate buffers
// *********************************
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include "serial.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 */
// 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
void interrupt com1_interrupt_driver();
void interrupt com2_interrupt_driver();
void interrupt com3_interrupt_driver();
void interrupt com4_interrupt_driver();
// *********************************
// Global Data *
// *********************************
//#define TX_QUEUE_SIZE 16 /* Transmit queue size. Change to suit */
//#define RX_QUEUE_SIZE 4096 /* Receive queue size. Change to suit */
typedef struct uart {
// 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 are held here until the UART is ready to transmit them.
char *tx_queue; // transmit queue
int tx_queue_size;// transmit 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()
char *rx_queue; // receive queue
int rx_queue_size;// recieve 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 */
} Uart;
// one structure for each port.
// If more than 4, be sure to add proper comN_interrupt_driver()
static Uart ports[MAX_PORT];
#define GetPort(num) (&ports[num-1])
// *********************************
// com_install()
// *********************************
// DESCRIPTION: Installs the communications drivers.
//
// SYNOPSIS:status = com_install(int portnum, int tx_size, int rx_size);
// int portnum; Desired port number
// int tx_size; size of transmit queue
// int rx_size; size of receive queue
// int status;
// 0 = Successful installation
// 1 = Invalid port number
// 2 = No UART for specified port
// 3 = Drivers already installed
// 4 = Cant allocate queue buffers
//
// Note: min size of queues will be 10 characters if give 0
// *********************************
const int uart_base[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8 };
const char intnums[] = { 0x0C, 0x0B, 0x0C, 0x0B };
const char i8259levels[] = { 4, 3, 4, 3 };
// Install Com handler on Portnum with Queue sizes indicated
int com_install(int portnum, int tx_size, int rx_size)
{
Uart *p;
// Port number out of bounds
if ((portnum < 1) || (portnum > MAX_PORT))
return 1;
p = GetPort(portnum);
if (p->com_installed) // Drivers already installed
return 3;
p->uart_data = uart_base[portnum-1]; // Set UART I/O addresses
p->uart_ier = p->uart_data + IER; /* for the selected comm */
p->uart_iir = p->uart_data + IIR; /* port */
p->uart_lcr = p->uart_data + LCR;
p->uart_mcr = p->uart_data + MCR;
p->uart_lsr = p->uart_data + LSR;
p->uart_msr = p->uart_data + MSR;
p->intnum = intnums[portnum-1]; /* Ditto for interrupt */
p->i8259bit = 1 << i8259levels[portnum-1]; //vector and 8259 bit mask
p->old_ier = inportb(p->uart_ier); /* Return an error if we */
outportb(p->uart_ier, 0); /* can't access the UART */
if (inportb(p->uart_ier) != 0)
return 2;
if (tx_size)
{
p->tx_queue = malloc(tx_size);
p->tx_queue_size = tx_size;
}
else
{
p->tx_queue = malloc(10);
p->tx_queue_size = tx_size;
}
if (!p->tx_queue)
return 4;
if (rx_size)
{
p->rx_queue = malloc(rx_size);
p->rx_queue_size = rx_size;
}
else
{
p->rx_queue = malloc(10);
p->rx_queue_size = rx_size;
}
if (!p->rx_queue)
{
free(p->tx_queue);
p->tx_queue=NULL;
return 4;
}
disable();
// Save the original 8259 mask, then disable the 8259 for this interrupt
p->old_i8259_mask = inportb(0x21);
outportb(0x21, p->old_i8259_mask | p->i8259bit);
enable();
com_flush_tx(portnum); // Clear the transmit and
com_flush_rx(portnum); // receive queues
// Save old COMM vector, & install new one
p->old_vector = getvect(p->intnum);
switch (portnum)
{
case 1:
setvect(p->intnum, com1_interrupt_driver);
break;
case 2:
setvect(p->intnum, com2_interrupt_driver);
break;
case 3:
setvect(p->intnum, com3_interrupt_driver);
break;
case 4:
setvect(p->intnum, com4_interrupt_driver);
break;
}
p->com_installed = TRUE; /* and note that we did */
// 8 data, no parity, 1 stop */
outportb(p->uart_lcr, DATA8 + NOPAR + STOP1);
disable(); /* Save MCR, then enable */
p->old_mcr = inportb(p->uart_mcr); /* interrupts onto the bus, */
outportb(p->uart_mcr, /* activate RTS and leave */
(p->old_mcr & DTR) | (OUT2 + RTS)); /* DTR the way it was */
enable();
outportb(p->uart_ier, DR); /* Enable receive interrupts */
// Now enable the 8259 for this interrupt
disable();
outportb(0x21, inportb(0x21) & ~(p->i8259bit));
enable();
// Successful installation
return 0;
}
// *********************************
// com_deinstall()
// *********************************
// 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.
//
// *********************************
void com_deinstall(int portnum)
{
Uart *p= GetPort(portnum);
if (p->com_installed) { /* Don't de-install twice! */
outportb(p->uart_mcr, p->old_mcr); /* Restore the UART */
outportb(p->uart_ier, p->old_ier); /* registers ... */
disable();
outportb(0x21, /* ... the 8259 interrupt */
(inportb(0x21) & ~p->i8259bit) | /* mask ... */
(p->old_i8259_mask & p->i8259bit));
enable();
setvect(p->intnum, p->old_vector); /* ... and the comm */
p->com_installed = FALSE; /* interrupt vector */
// free queues
free(p->tx_queue); p->tx_queue = NULL;
free(p->rx_queue); p->rx_queue = NULL;
}
}
// *********************************
// 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.)
//
// *********************************
void com_set_speed(int portnum, long speed)
{
unsigned divisor; /* A local temp */
register Uart *p= GetPort(portnum);
if (p->com_installed) {
if (speed < 1) speed = 1; /* Force proper input */
divisor = 119000L / speed; /* Recond baud rate divisor 152000 */
disable(); /* Interrupts off */
outportb(p->uart_lcr, /* Set up to load baud rate */
inportb(p->uart_lcr) | 0x80); /* divisor into UART */
outport(p->uart_data, divisor); /* Do so */
outportb(p->uart_lcr, /* Back to normal UART ops */
inportb(p->uart_lcr) & ~0x80);
enable(); /* Interrupts back on */
}
}
// *********************************
// 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
//
// *********************************
const char lcr_vals[] = {
DATA8 + NOPAR,
DATA7 + EVNPAR,
DATA7 + ODDPAR,
DATA7 + STKPAR,
DATA7 + ZROPAR
} ;
void com_set_parity(int portnum, enum par_code parity, int stop_bits)
{
Uart *p= GetPort(portnum);
if (!p->com_installed) return;
disable();
outportb(p->uart_lcr, lcr_vals[parity] | ((stop_bits == 2)?STOP2:STOP1));
enable();
}
void com_pulse_rts(int portnum)
{
Uart *p= GetPort(portnum);
if (!p->com_installed) return;
outportb(p->uart_mcr,0x00); /* clear RTS */
delay(500);
outportb(p->uart_mcr,0x02); /* set RTS */
delay(500);
}
// *********************************
// com_raise_dtr() *
// com_lower_dtr() *
// *********************************
// DESCRIPTION: These routines raise and lower the DTR line.
// Lowering DTR causes most modems to hang up.
//
// *********************************
void com_lower_dtr(int portnum)
{
Uart *p= GetPort(portnum);
if (p->com_installed) {
disable();
outportb(p->uart_mcr, inportb(p->uart_mcr) & ~DTR);
enable();
}
}
void com_raise_dtr(int portnum)
{
Uart *p= GetPort(portnum);
if (p->com_installed) {
disable();
outportb(p->uart_mcr, inportb(p->uart_mcr) | DTR);
enable();
}
}
// *********************************
// 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
//
// *********************************
void com_tx(int portnum, char c)
{
Uart *p= GetPort(portnum);
if (p->com_installed)
{
while (!com_tx_ready(portnum))
; /* Wait for non-full buffer */
disable(); // Interrupts off
p->tx_queue[p->tx_in++] = c; // Stuff character in queue
if (p->tx_in == p->tx_queue_size)
p->tx_in = 0; // Wrap index if needed
p->tx_chars++; // Number of char's in queue
// Enable UART tx interrupt
outportb(p->uart_ier, inportb(p->uart_ier) | THRE);
enable(); // Interrupts back on */
}
}
void com_tx_string(int portnum, char *s)
{
Uart *p= GetPort(portnum);
if (!p->com_installed) return;
while (*s) com_tx(portnum, *s++); // Send the 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
//
// *********************************
unsigned char com_rx(int portnum)
{
Uart *p= GetPort(portnum);
char rv; /* Local temp */
if (!p->rx_chars || !p->com_installed) /* Return NULL if receive */
return '\0'; /* buffer is empty */
disable(); /* Interrupts off */
rv = p->rx_queue[p->rx_out++]; /* Grab char from queue */
if (p->rx_out == p->rx_queue_size) /* wrap index if needed */
p->rx_out = 0;
p->rx_chars--; /* One less char in queue */
enable(); /* Interrupts back on */
return rv; /* The answer! */
}
// *********************************
// * Queue Status Routines
// **********************************
// DESCRIPTION: Small routines to return status of the transmit
// and receive queues.
//
// **********************************
int com_tx_ready(int portnum)
{ /* Return TRUE if the */
Uart *p= GetPort(portnum);
return ((p->tx_chars < p->tx_queue_size) || /* transmit queue can */
(!p->com_installed));
}
int com_tx_empty(int portnum)
{ // Return TRUE if the transmit queue is empty
Uart *p= GetPort(portnum);
return (!p->tx_chars || (!p->com_installed));
}
int com_rx_empty(int portnum)
{ // Return TRUE if the receive queue is empty
Uart *p= GetPort(portnum);
return (!p->rx_chars || (!p->com_installed));
}
int com_rx_count(int portnum)
{ // Return TRUE if the receive queue is empty
Uart *p= GetPort(portnum);
if (!p->com_installed) return(0);
return p->rx_chars;
}
// ****************************************
// com_flush_tx() *
// com_flush_rx() *
// ****************************************
// DESCRIPTION: Buffer flushers! These guys just initialize the transmit
// and receive queues (respectively) to their empty state.
// *
// *********************************
void com_flush_tx(int portnum)
{
register Uart *p= GetPort(portnum);
disable();
p->tx_chars = p->tx_in = p->tx_out = 0;
enable();
}
void com_flush_rx(int portnum)
{
register Uart *p= GetPort(portnum);
disable();
p->rx_chars = p->rx_in = p->rx_out = 0;
enable();
}
// ***********************************
// com_carrier()
// ***********************************
// DESCRIPTION: Returns TRUE if a carrier is present.
//
// *********************************
int com_carrier(int portnum)
{
register Uart *p= GetPort(portnum);
if (p->com_installed)
return(inportb(p->uart_msr) & RLSD);
else return(0);
}
int com_cts(int portnum)
{
register Uart *p= GetPort(portnum);
if (p->com_installed)
return(inportb(p->uart_msr) & CTS);
else return(0);
}
int com_modem_stat(int portnum)
{
register Uart *p= GetPort(portnum);
if (p->com_installed)
return(inportb(p->uart_msr));
else return(0);
}
// *********************************
// * com_interrupt_driver()
// *********************************
// DESCRIPTION: Handles communications interrupts.
// The UART will interruptwhenever 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 com_driver(register Uart *p);
void interrupt com1_interrupt_driver()
{
disable();
com_driver(&ports[0]);
enable();
}
void interrupt com2_interrupt_driver()
{
disable();
com_driver(&ports[1]);
enable();
}
void interrupt com3_interrupt_driver()
{
disable();
com_driver(&ports[2]);
enable();
}
void interrupt com4_interrupt_driver()
{
disable();
com_driver(&ports[3]);
enable();
}
void com_driver(register Uart *p)
{
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 there is an int ...
while (!((iir = inportb(p->uart_iir)) & 1))
{
// Branch on interrupt type
switch (iir)
{
case 0: // Modem status interrupt
inportb(p->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 (p->tx_chars <= 0)
// If tx buffer empty, turn off transmit interrupts
outportb(p->uart_ier, inportb(p->uart_ier) & ~2);
else
{ // Tx buffer not empty
if (inportb(p->uart_lsr) & TXR) {
outportb(p->uart_data, p->tx_queue[p->tx_out++]);
if (p->tx_out == p->tx_queue_size)
p->tx_out = 0;
p->tx_chars--;
}
} // End 'tx buffer not empty
break;
case 4: // Received data interrupt
c = inportb(p->uart_data); // Grab received character
if (p->rx_chars < p->rx_queue_size)
{ // If queue not full, save the new character
p->rx_queue[p->rx_in++] = c;
if (p->rx_in == p->rx_queue_size) // wrap index if needed
p->rx_in = 0;
p->rx_chars++; // Count the new character
} /* End queue not full */
break;
case 6: // Line status interrupt
inportb(p->uart_lsr); // Just clear the interrupt
break;
} /* End switch */
} /* End 'is an interrupt' */
outportb(0x20, 0x20); /* Send EOI to 8259 */
}