home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
vol_300
/
307_01
/
comx.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-03-20
|
25KB
|
742 lines
/*
HEADER: ;
TITLE: PC com port driver;
VERSION: 1.0;
DESCRIPTION: "MS-DOS serial port device driver. Provides buffered
input and output to serial port with optional XON/XOFF
flow control through standard read/write requests or
interrupt 0x14.
Mixed memory model used. COMX.C compiled as small
model with explicitly declared far pointers. Front
end program coerces the linkage editor to produce a
tiny model executable.";
WARNINGS: "Microsoft specific. Startup code contained in
COMXFE.ASM does not initialize uninitialized static
variables to zero.";
KEYWORDS: Serial communications, device driver;
SYSTEM: MS-DOS v2 or later;
FILENAME: COMX.C;
SEE-ALSO: COMX, COMXFE.ASM, COMXBE.C;
AUTHORS: Hugh Daschbach;
COMPILERS: Microsoft v5.0,v5.1;
*/
/*----------------------------------------------------------------------*/
/* comx.c: MS-DOS driver for multiple communication devices (com1 - com4).
*/
#include "comx.h"
/* Instalation:
* This program is installed as an MS-DOS device driver by including
* the following entry in the CONFIG.SYS file:
* DEVICE = COMX.SYS xxx [xxx [xxx [xxx]]]
* where xxx is a com port address in hexadecimal (e.g. 3fa). At least
* one port is required. The driver will support as many as four.
*
* The communication devices are assumed to interrupt on either IRQ3 or
* IRQ4. More than one device can share the same interrupt line as
* each defined port is polled on every interrupt. Other versions of
* this driver support dedicated interrupt vectors, eliminating the
* polling operation at interrupt time. However, even with the polling
* overhead, the driver has been tested successfully at 9600 baud on a
* 4.77 Mhz 8088.
*/
/*----------------------------------------------------------------------*/
/* System Declarations */
/*----------------------------------------------------------------------*/
#pragma pack(1)
int inp(unsigned int);
int outp(unsigned int, int);
void _enable(void);
void _disable(void);
#pragma intrinsic(inp, outp, _enable, _disable)
/*----------------------------------------------------------------------*/
/* Local Data Declarations */
/*----------------------------------------------------------------------*/
typedef unsigned char uchar;
typedef unsigned short ushort;
/*----------------------------------------------------------------------*/
/* External Function Prototypes. */
/*----------------------------------------------------------------------*/
extern void int10(unsigned ax, unsigned bx);
/*----------------------------------------------------------------------*/
/* Local function prototypes */
/*----------------------------------------------------------------------*/
static void check_block(struct comport *chan);
static int com_int(void);
static char *define_lines(char far *cp);
extern void driver(struct rb far *rbp, int line_no);
static int eol(int c);
static void get_char(struct comport *chan);
static void hookvect(unsigned int intnum, struct isr_block *isr,
void (cdecl *handler)());
static int int14(ushort ax, ushort bx, ushort cx,
ushort dx, ushort ds, ushort es);
static int isspace(int c);
static int nop(int i);
static void put_char(struct comport *chan);
static void putc(int c);
static void puts(char *s);
static void putx(int i);
static void setspeed(struct comport *chan, int sti);
/*----------------------------------------------------------------------*/
/* Driver interface declarations */
/*----------------------------------------------------------------------*/
enum opcodes { /* Driver operation codes: */
INIT = 0, /* initialization */
MEDIA_CHECK, /* media check */
BUILD_BPB, /* build BIOS Parameter Block */
INPUT_IOCTL, /* input control string */
INPUT, /* read */
NON_DEST_INPUT, /* non destructive input */
INPUT_STATUS, /* input available status */
INPUT_FLUSH, /* flush input buffer */
OUTPUT, /* write */
OUTPUT_VERIFY, /* write with verify */
OUTPUT_STATUS, /* output busy status */
OUTPUT_FLUSH, /* flush output buffer */
OUTPUT_IOCTL, /* output control string */
DEVICE_OPEN, /* device open (removable media) */
DEVICE_CLOSE, /* device close (removable media) */
REMOVEABLE_MEDIA, /* more removable media */
GENERIC_IOCTL = 19, /* more ioctl */
GET_LOGICAL_DEVICE = 23, /* get logical device */
SET_LOGICAL_DEVICE = 24 /* set logical device */
};
/* Driver status indicators: */
#define DONE 0x0100 /* I/O done */
#define BUSY 0x0200 /* device busy */
#define ERROR 0x8000 /* I/O error */
enum errcodes { /* Driver error codes: */
WRITE_PROTECT, /* write protect violation */
UNKNOWN_UNIT, /* unknown unit */
NOT_READY, /* device non ready */
UNKNOWN_COMMAND, /* unknown command */
CRC_ERROR, /* CRC error */
BAD_STRUCT_LENGTH, /* bad drive request structure length */
SEEK_ERROR, /* seek error */
UNKNOWN_MEDIA, /* unknown media */
SECTOR_NOT_FOUND, /* sector not found */
PAPER_OUT, /* printer out of paper */
WRITE_FAULT, /* write fault */
READ_FAULT, /* read fault */
GENERAL_FAILURE, /* general failure */
RESV_D, /* reserved */
RESV_E, /* reserved */
INVALID_DISK_CHANGE /* invalid disk change */
};
typedef struct rb { /* I/O Request block */
uchar len; /* request block length */
uchar unit; /* unit code */
uchar op_code; /* operation code */
int status; /* status code */
char resv[8]; /* reserved */
union {
struct {
uchar count; /* unit count */
char far *end; /* end of resident module */
char far *bpbp; /* BPB array pointer */
uchar drive; /* drive number */
} init;
struct {
uchar media; /* media descriptor byte */
char far *buf; /* transfer buffer pointer */
int count; /* byte/sector count */
int sector; /* starting sector number */
char far *vol_id; /* volume idenitifier pointer */
} inout;
char ndi; /* non destructive input data byte */
} os; /* operation specific */
} rb;
/*----------------------------------------------------------------------*/
/* Interrupt service routine interface declaration */
/*----------------------------------------------------------------------*/
typedef struct isr_block { /* Interrupt Service Routine */
uchar push_es;
uchar push_ds;
uchar push_dx;
uchar push_cx;
uchar push_bx;
uchar push_ax;
ushort mov_ax_cs;
ushort mov_ds_ax;
uchar call;
ushort target;
ushort or_ax_ax;
uchar pop_ax;
uchar pop_bx;
uchar pop_cx;
uchar pop_dx;
uchar pop_ds;
uchar pop_es;
ushort jz_ip01;
uchar cs_prefix;
ushort jmp;
void (far **fnpp)(void);
uchar iret;
void (far *fnp)(void);
} isr_block;
/*----------------------------------------------------------------------*/
/* Hardware interface declarations */
/*----------------------------------------------------------------------*/
/* Line Status Register definition */
#define LSR_RDA 1 /* receive data available */
#define LSR_OR 2 /* overrun error */
#define LSR_PE 4 /* parity error */
#define LSR_FE 8 /* framing error */
#define LSR_BI 0x10 /* break interupt */
#define LSR_TBA 0x20 /* transmit holding buffer available */
#define LSR_TSRE 0x40 /* transmit shift buffer empty */
/* Modem Control Register definitions */
#define MCR_DTR 1 /* data terminal ready */
#define MCR_RTS 2 /* request to send */
#define MCR_OUT1 4 /* reset Hayes Smartmodem */
#define MCR_OUT2 8 /* enable Hayes Smartmodem interrupts */
#define MCR_LOOP 0x10 /* loop back test enable */
/* Interrupt Identification Register */
#define IIR_PEND 1 /* interrupt pending */
#define IIR_MASK 6 /* select following values: */
#define IIR_RLS 6 /* receiver line status (error) */
#define IIR_RDA 4 /* receive data available */
#define IIR_TBA 2 /* transmit buffer available */
#define IIR_MS 0 /* modem status */
/* Interrupt Enable Register definition */
#define IER_RDA 1 /* receive data available interrupt */
#define IER_TBA 2 /* transmit buffer available interrupt */
#define IER_RLS 4 /* receive line status interrupt */
#define IER_MS 8 /* modem status interrupt */
/* Line Control Register definition */
#define LCR_WLS0 1 /* word length select bit 0 */
#define LCR_WLS1 2 /* word length select bit 1 */
#define LCR_STB 4 /* number of stop bits */
#define LCR_PEN 8 /* parity enable */
#define LCR_EPS 16 /* even parity select */
#define LCR_STICK 32 /* stick parity */
#define LCR_BREAK 64 /* transmit break */
#define LCR_DLAB 128 /* divisor latch access bit */
/* Modem Status Register definition */
#define MSR_DCTS 0x00001 /* delta clear to send */
#define MSR_DDSR 0x00002 /* delta data set ready */
#define MSR_TERI 0x00004 /* trailing edge ring indicator */
#define MSR_DCD 0x00008 /* carrier detect */
#define MSR_CTS 0x00010 /* clear to send */
#define MSR_DSR 0x00020 /* data set ready */
#define MSR_RI 0x00040 /* ring indicator */
#define MSR_CD 0x00080 /* carrier detect */
typedef enum comregs { /* INS8250 registers addresses */
rx = 0, /* receive data register */
tx = 0, /* transmit data register */
ie, /* interrupt enable register */
ii, /* interupt identification register */
lc, /* line control register */
mc, /* modem control register */
ls, /* line status register */
ms, /* modem status register */
dl_lsb = 0, /* divisor latch least significant byte */
dl_msb /* divisor latch most significant byte */
} comregs;
#define PIC 0x20 /* 8259A Programmable Interrupt Controller */
#define EOI 0x20 /* End of Interrupt command to PIC */
/*----------------------------------------------------------------------*/
/* Local Data Definitions */
/*----------------------------------------------------------------------*/
#define OBUFSZ 128 /* output character fifo size */
/* next output buffer position */
#define next_out(i) ((i + 1) & (OBUFSZ - 1))
/* pervious output buffer position */
#define prev_out(i) ((i - 1) & (OBUFSZ - 1))
#define IBUFSZ 128 /* input character fifo size */
/* next input buffer position */
#define next_in(i) ((i + 1) & (IBUFSZ - 1))
/* pervious input buffer position */
#define prev_in(i) ((i - 1) & (IBUFSZ - 1))
#define THOLD_OFF (IBUFSZ/2) /* input char count b4 sending XOFF */
#define THOLD_ON (THOLD_OFF/2) /* input char count b4 sending XON */
#define XON 0x11 /* CTRL/Q */
#define XOFF 0x13 /* CTRL/S */
typedef struct comport {
unsigned port; /* base port address */
uchar *out_buf; /* output buffer pointer */
unsigned *in_buf; /* input buffer pointer */
uchar flow; /* flow control enabled */
uchar in_block; /* we've transmitted X-OFF */
uchar out_block; /* we've received X-OFF */
int out_head; /* index to next empty buffer position */
int out_tail; /* index to last printed character */
int in_head; /* index to next empty buffer position */
int in_tail; /* index to last returned character */
} comport;
comport comx[4] = { { 0 }, { 0 }, { 0 }, { 0 } };
static int port_count; /* defined port count */
static unsigned speed_tbl[] = { /* baud rate generator divisor latch */
1047, 768, 384, 192, 96, 48, 24, 12, 6, 3
};
isr_block isr0c; /* interrupt 0x0c service routine */
isr_block isr0b; /* interrupt 0x0b service routine */
isr_block isr14; /* interrupt 0x14 service routine */
#ifdef DEBUG
/*----------------------------------------------------------------------*/
/* Diagnostic output routines */
/*----------------------------------------------------------------------*/
#define putchar(c) int10((14 << 8) + c, 0)
#define printnl(s) { puts(#s " = "); putx(s); puts("\n\r"); }
#define print(s) { puts(#s " = "); putx(s); puts(" "); }
#define dumpc(c) { puts(#c " = "); putc(c); puts(" "); }
char *to_print = "0123456789abcdef";
static void putc(int c)
{
putchar(to_print[c >> 4 & 0xf]);
putchar(to_print[c & 0xf]);
}
static void puts(char *s)
{
for (; *s; s++)
putchar(*s);
}
static void putx(int i)
{
putchar(to_print[i >> 12 & 0xf]);
putchar(to_print[i >> 8 & 0xf]);
putchar(to_print[i >> 4 & 0xf]);
putchar(to_print[i & 0xf]);
}
#endif
/*----------------------------------------------------------------------*/
/* Common service routines */
/*----------------------------------------------------------------------*/
static int nop(i) /* flush instruction prefetch queue */
{ /* for timing btwn inp & outp */
return(i);
}
static void setspeed(register comport *chan, int sti)
{
int lcr;
lcr = inp(chan->port + lc); (void) nop(0);
outp(chan->port + lc, lcr + LCR_DLAB); (void) nop(0);
outp(chan->port + dl_msb, speed_tbl[sti] >> 8); (void) nop(0);
outp(chan->port + dl_lsb, speed_tbl[sti] & 0xff); (void) nop(0);
outp(chan->port + lc, lcr);
}
/*----------------------------------------------------------------------*/
/* Interrupt service routines */
/*----------------------------------------------------------------------*/
static int com_int()
{
register comport *chan; /* current comx[] element pointer */
int int_id;
char active;
_enable(); /* reenable interrupts */
do {
active = 0;
for (chan = comx; chan->port; chan++)
{
while (((int_id = inp(chan->port + ii)) & IIR_PEND) == 0)
{
active = 1;
switch (int_id & IIR_MASK)
{
case IIR_RDA: /* rx data avalable */
get_char(chan);
break;
case IIR_RLS: /* receiver line status */
chan->in_buf[chan->in_head] = inp(chan->port + ls) << 8;
chan->in_head = next_in(chan->in_head);
break;
case IIR_TBA: /* tx buffer available */
put_char(chan);
break;
case IIR_MS: /* Modem status change */
inp(chan->port + ms);
break;
}
}
}
} while (active);
outp(PIC, EOI); /* reenable PIC interrupts */
return(0); /* skip the BIOS routines on exit */
}
/*----------------------------------------------------------------------*/
static void get_char(chan)
register comport *chan; /* current comx[] element pointer */
{
unsigned c;
int line_stat;
int count;
if ((line_stat = inp(chan->port + ls)) & LSR_RDA)
{
c = (unsigned) inp(chan->port + rx);
if ((chan->flow & PROT_XOUT) != 0 && c == XOFF)
chan->out_block = 1;
else if ((chan->flow & PROT_XOUT) != 0 && c == XON)
{
chan->out_block = 0;
put_char(chan);
}
else
{
if (next_in(chan->in_head) == chan->in_tail)
chan->in_buf[chan->in_head] |= LSR_OR << 8;
else
{
chan->in_buf[chan->in_head] = c | ((unsigned) line_stat << 8);
chan->in_head = next_in(chan->in_head);
}
if ((chan->flow & PROT_XIN) != 0)
{
if ((count = chan->in_head - chan->in_tail) < 0)
count += IBUFSZ;
if (count > THOLD_OFF && !chan->in_block)
{
chan->out_tail = prev_out(chan->out_tail);
chan->out_buf[chan->out_tail] = XOFF;
put_char(chan);
chan->in_block = 1;
}
} /* flow control enabled */
} /* received XON or XOFF */
} /* receive data available */
}
/*----------------------------------------------------------------------*/
static void put_char(chan)
register comport *chan; /* current comx[] element pointer */
{
if (inp(chan->port + ls) & LSR_TBA &&
!chan->out_block &&
chan->out_head != chan->out_tail)
{
outp(chan->port + tx, chan->out_buf[chan->out_tail]);
chan->out_tail = next_out(chan->out_tail);
}
}
/*----------------------------------------------------------------------*/
static void check_block(chan) /* should we transmit XON ? */
register comport *chan;
{
int count;
if (chan->in_block)
{
if ((count = chan->in_head - chan->in_tail) < 0)
count += IBUFSZ;
if (count < THOLD_ON)
{
chan->out_tail = prev_out(chan->out_tail);
chan->out_buf[chan->out_tail] = XON;
put_char(chan);
chan->in_block = 0;
}
}
}
/*----------------------------------------------------------------------*/
/* Initialization */
/*----------------------------------------------------------------------*/
static int eol(c) { return(c == '\r' || c == '\n'); }
static int isspace(c) { return(c == ' ' || c == '\t'); }
static char *define_lines(char far *cp)
{
extern char near bss_end; /* defined in comxbe.c */
register comport *chan; /* current comx[] element pointer */
char *bp; /* buffer pointer */
int port; /* port address */
bp = &bss_end; /* initial buffer pointer */
chan = comx;
while (!eol(*cp) && isspace(*cp))
cp++;
while (!eol(*cp) && !isspace(*cp))
cp++;
while (!eol(*cp) && isspace(*cp))
cp++;
while (!eol(*cp))
{
port = 0;
while (!eol(*cp) && !isspace(*cp))
{
port = (port << 4) +
((*cp >= '0' && *cp <= '9') ? *cp - '0' :
(*cp >= 'a' && *cp <= 'f') ? *cp - 'a' + 10 :
(*cp >= 'A' && *cp <= 'F') ? *cp - 'A' + 10 : 0);
cp++;
}
chan->port = port;
outp(chan->port + mc, MCR_DTR | MCR_RTS | MCR_OUT2);
chan->in_buf = (unsigned *) bp;
bp += IBUFSZ * sizeof(unsigned);
chan->out_buf = (uchar *) bp;
bp += OBUFSZ * sizeof(uchar);
chan++;
while (!eol(*cp) && isspace(*cp))
cp++;
}
port_count = chan - comx;
return(bp);
}
/*----------------------------------------------------------------------*/
static void hookvect(unsigned intnum,
isr_block *isr,
void (*handler)())
{
void (far *(far *vect_ad))();
extern isr_block isr_prototype;
*isr = isr_prototype;
isr->target = (char *) handler - (char *) &isr->call - 3;
vect_ad = (void (far *(far *))()) ((long) (intnum * 4));
isr->fnp = *vect_ad;
isr->fnpp = &isr->fnp;
*vect_ad = (void (far *)()) isr;
}
/*----------------------------------------------------------------------*/
/* DOS driver */
/*----------------------------------------------------------------------*/
void driver(rbp, line_no)
rb far *rbp;
int line_no;
{
comregs *(far *chan_ptr) = (comregs *(far *)) 0x00400006l;
register comport *chan; /* current comx[] element pointer */
static char *endp; /* end of driver pointer */
int i;
chan = comx + line_no;
switch (rbp->op_code)
{
case INIT:
if (line_no == 0)
{
endp = define_lines(rbp->os.init.bpbp);
hookvect(0x0c, &isr0c, com_int);
hookvect(0x0b, &isr0b, com_int);
hookvect(0x14, &isr14, int14);
}
rbp->os.init.end = (char far *) endp;
break;
case INPUT_IOCTL:
if ((i = chan->in_head - chan->in_tail) < 0)
i += IBUFSZ;
if (rbp->os.inout.count == 2)
*((int far *) rbp->os.inout.buf) = i;
else
rbp->status |= ERROR | READ_FAULT;
break;
case INPUT:
while (chan->in_head == chan->in_tail)
;
for (i = 0;
i < rbp->os.inout.count && chan->in_head != chan->in_tail;
i++)
{
if (chan->in_buf[chan->in_tail] &
((LSR_OR | LSR_PE | LSR_FE | LSR_BI) << 8))
{
rbp->status |= ERROR | READ_FAULT;
break;
}
rbp->os.inout.buf[i] = (char) chan->in_buf[chan->in_tail];
chan->in_tail = next_in(chan->in_tail);
}
rbp->os.inout.count = i;
check_block(chan);
break;
case NON_DEST_INPUT:
if (chan->in_head == chan->in_tail)
rbp->status |= BUSY;
else
rbp->os.ndi = (char) chan->in_buf[chan->in_tail];
break;
case INPUT_STATUS:
rbp->status |= (chan->in_head != chan->in_tail) ? 0 : BUSY;
break;
case INPUT_FLUSH:
chan->in_tail = chan->in_head;
break;
case OUTPUT:
case OUTPUT_VERIFY:
for (i = 0; i < rbp->os.inout.count; i++)
{
if (next_out(chan->out_head) == chan->out_tail)
{
rbp->status |= BUSY;
break;
}
chan->out_buf[chan->out_head] = rbp->os.inout.buf[i];
chan->out_head = next_out(chan->out_head);
}
rbp->os.inout.count = i;
break;
case OUTPUT_STATUS:
rbp->status |= (chan->out_head == chan->out_tail) ? 0 : BUSY;
break;
case OUTPUT_FLUSH:
chan->out_tail = chan->out_head;
break;
default:
rbp->status |= ERROR | UNKNOWN_COMMAND;
}
/* it seems wasteful to reset the interrupt
* masks on every I/O operation. But
* usurping communication programs do not
* always reset the hardware regs correctly
* upon termination. we pay the price here.
*/
outp(PIC + 1, inp(PIC + 1) & 0xe7);
/* (re)loading the UART interrupt enable
* register pops a tx bufffer available
* interrupt (if it actually is). This
* initiates output data transfer after
* data is written to the output buffer.
*/
outp(chan->port + ie, IER_RDA | IER_TBA | IER_RLS);
rbp->status |= DONE;
}
/*----------------------------------------------------------------------*/
/* BIOS int 14h interface */
/*----------------------------------------------------------------------*/
static int int14(ax, bx, cx, dx, ds, es)
ushort ax, bx, cx, dx, ds, es;
{
static unsigned status;
register comport *chan; /* current comx[] element pointer */
char far *cfp;
_enable();
if (dx > port_count)
return(0);
chan = comx + dx;
switch (ax >> 8)
{
case INIT_232: /* initialize port */
outp(PIC + 1, inp(PIC + 1) & 0xe7);
outp(chan->port + mc, 0);
setspeed(chan, (ax >> 5) & 7);
outp(chan->port + lc, ax & 0x1f);
outp(chan->port + mc, MCR_DTR | MCR_RTS | MCR_OUT2);
break;
case PUTC_232: /* put character */
if (next_out(chan->out_head) == chan->out_tail)
{
ax = 0x8000;
break;
}
chan->out_buf[chan->out_head] = (char) (ax & 0xff);
chan->out_head = next_out(chan->out_head);
outp(chan->port + ie, IER_RDA | IER_TBA | IER_RLS);
break;
case GETC_232: /* get character */
if (chan->in_head == chan->in_tail)
{
ax = 0x8000;
break;
}
ax = chan->in_buf[chan->in_tail];
chan->in_tail = next_in(chan->in_tail);
check_block(chan);
break;
case STAT_232: /* fetch status. Use status saved by */
/* string read if any, or current */
/* hardware if not. */
if (status == 0)
ax = (inp(chan->port + ls) << 8) + inp(chan->port + ms);
else
{
ax = status;
status = 0;
}
break;
case WRITE_232: /* write string */
cfp = (char far *) ((unsigned long) es << 16) + bx;
for (ax = 0; ax < cx; ax++)
{
if (next_out(chan->out_head) == chan->out_tail)
{
ax |= 0x8000;
break;
}
chan->out_buf[chan->out_head] = *cfp++;
chan->out_head = next_out(chan->out_head);
}
outp(chan->port + ie, IER_RDA | IER_TBA | IER_RLS);
break;
case READ_232: /* read string */
cfp = (char far *) ((unsigned long) es << 16) + bx;
for (ax = 0; ax < cx; ax++)
{
if (chan->in_buf[chan->in_tail] &
((LSR_OR | LSR_PE | LSR_FE | LSR_BI) << 8))
{
ax |= 0x8000; /* signal error, save status for */
/* subsequent inquiry */
status = chan->in_buf[chan->in_tail] & ~0xff +
inp(chan->port + ms);
chan->in_tail = next_in(chan->in_tail);
break;
}
*cfp++ = (char) chan->in_buf[chan->in_tail];
chan->in_tail = next_in(chan->in_tail);
}
check_block(chan);
break;
case ICOUNT_232: /* fetch input character count */
if ((ax = chan->in_head - chan->in_tail) < 0)
ax += IBUFSZ;
break;
case OCOUNT_232: /* fetch output buffer available */
ax = (chan->out_head >= chan->out_tail) ?
OBUFSZ - (chan->out_head - chan->out_tail) :
chan->out_tail - chan->out_head;
break;
case SETPROT_232: /* set protocol */
chan->flow = (uchar) ax;
break;
case SETSPEED_232: /* set line speed */
if (cx > num_entries(speed_tbl))
ax = 0x8000; /* report invalid speed */
else
setspeed(chan, cx);
break;
}
return(0);
}