[comp.sys.ibm.pc.programmer] serial.c interrupt handler in C

mac@idacrd.UUCP (Robert McGwier) (03/03/90)

Microsoft C lets you define ISR's.  Here's code to do what you want if you
decide that Microsoft might do the job.  I understand that Turbo C has
copied this function type.  asc_enab enables the ISR, asc_disab disables that
port.  I have included code that could be used to do both COM1 and COM2
simultaneously.  You should call signal and replace the control break signal
with a dummy routine.  If you don't, the first time a character comes
in the serial port and you have control C ended then routine, the
system will most likely hang.  Writing serial interrupt handlers
is one of the most trivial exercises a beginning programmer can do with
the PC.  It is disgusting beyond belief to me that IBM, etc. never did
this in the BIOS.  I have run this code at 19200 by programming the
hardware directly as mentioned in an earlier article on a 4.77 Mhz
PC.  I don't have the baud rate programming code at this machine,
I could bring it in as well but you can always call the BIOS via
int86(0x14, etc.) to do that unless you need to run at 19200.
Of course, at this level, the code is extremely hardware dependent.
The code can be used for COM3 and COM4 by changing/adding #defines
for those ports.  I have those routines, included in the base for
this code but I have NEVER tested it so I will not include it (I
don't own a COM3 or 4 board/port).

/* Copyright Bob McGwier, 1990.  ALL RIGHTS RESERVED */
/* May be used freely for non-commercial purposes without */
/* permission of the author */


#include <stdio.h>
#include <dos.h>
#include <malloc.h>
#define PIC_MASK 0x21
#define	PIC_EOI  0x20
#define ERR_MSK	 0x9E

/* Definitions for interrupt handling */

/*  COM1 */

#define COM_DATA_1 0x3F8	/* Data received on this I/O address */
#define COM_IER_1  0x3F9	/* This register enables interrupts */
#define COM_MCR_1  0x3FC	/* Control Register (signals) */
#define COM_STAT_1  0x3FD	/* Status Register */
#define COM_INT_1   0xC		/* 0xC handles IRQ4 or COM1 by standard */
#define INT_MASK_1  0x10	/* Mask for PIC (programmable interrupt
				   controller) 8259A */

/* COM2 */

#define COM_DATA_2 0x2F8	/* Data received on this I/O address */
#define COM_IER_2  0x2F9	/* This register enables interrupts */
#define COM_MCR_2  0x2FC	/* Control Register (signals) */
#define COM_STAT_2  0x2FD	/* Status Register */
#define COM_INT_2   0xB		/* 0xB handles IRQ3 or COM2 by standard */
#define INT_MASK_2  0x8		/* Mask for PIC (programmable interrupt
				   controller) 8259A */
static unsigned char *c1_buf;
static unsigned char *c2_buf;
static unsigned asc_in_1,asc_in_2,asc_old_1,asc_old_2;
static void interrupt cdecl far int_com1();
static void interrupt cdecl far int_com2();
static void far *old_c1,far *old_c2;


/* Instat is trivial.*/

/* If the ring buffer write pointer is not the same as the ring buffer
   read pointer, then a character is in the buffer */

int instat(com_port)
int com_port;
{
	switch(com_port) {
		case 0:{
			if (asc_old_1 == asc_in_1) return 0;
			else return 1;
			break;
		}
		case 1:{
			if (asc_old_2 == asc_in_2) return 0;
			else return 1;
			break;
		}
	}
}

/* If the ring buffer write pointer is not the same as the ring buffer
   read pointer, then a character is in the buffer */

int rcvbyte(com_port)
int com_port;
{
	int ch;
	switch(com_port) {
		case 0: {
			if (asc_old_1 == asc_in_1) return -1;
			else {
				ch = c1_buf[asc_old_1];
				asc_old_1 = (asc_old_1+1)&4095;
				return ch;
			}
		}
		case 1: {
			if (asc_old_2 == asc_in_2) return -1;
 			else {
				ch = c2_buf[asc_old_2];
				asc_old_2 = (asc_old_2+1)&4095;
				return ch;
			}
		}
	}
}

/*  COM1 Interrupt handler.  HARDWARE DEPENDENT */

static void interrupt cdecl far int_com1(es,ds,di,si,bp,sp,bx,dx,cx,ax)
 unsigned es,ds,di, si, bp, sp, bx, dx, cx, ax;
{
	char ch;
	_disable();		/* Disable interrupts while we move data
				   and pointers */
	if((ch = (inp(COM_STAT_1)&ERR_MSK)) == 0) {  /*If no error message */
		ch = inp(COM_DATA_1);	/* Get the character */
		c1_buf[asc_in_1] = ch;	/* Store data in circular buffer */
		asc_in_1 = (asc_in_1+1)&4095; /*iterate and wrap */
	} else ch = inp(COM_DATA_1);	/* Get the character */
	_enable();		/* Enable interrupts */
	outp(PIC_EOI,0x20);	/* Tell 8259A we have handled the interrupt */
}

/*  COM2 Interrupt handler.  HARDWARE DEPENDENT */

static void interrupt cdecl far int_com2(es,ds,di,si,bp,sp,bx,dx,cx,ax)
unsigned es,ds,di, si, bp, sp, bx, dx, cx, ax;
{
	char ch;
	_disable();		/* Disable interrupts while we move data
				   and pointers */
	if((ch = (inp(COM_STAT_2)&ERR_MSK)) == 0) {/* If no error message*/
		ch = inp(COM_DATA_2);	/* Get the character */
		c2_buf[asc_in_2] = ch;	/* Store data in circular buffer */
		asc_in_2 = (asc_in_2+1)&4095; /*iterate and wrap */
	} else ch=inp(COM_DATA_2);
	_enable();		/* Enable interrupts */
	outp(PIC_EOI,0x20);	/* Tell 8259A we have handled the interrupt */
}

/* 4096 character buffers are utilized, that is usually more than a screenful
	of data ;-) */
void asc_enab(com_port)
int com_port;
{
	char ch;
	switch(com_port) {
		case 0:{
			asc_in_1 = asc_old_1 = 0;
			if((c1_buf = (unsigned char *)malloc(4096)) == NULL) {
				perror("Out of memory");
				exit(0);
			}
			old_c1 = _dos_getvect(COM_INT_1);  /* Be a friendly
							      ISR and save the
							      old vector for
							      restoration */
			_dos_setvect(COM_INT_1,int_com1); /* Tell DOS whose got
							     the vector now */
			outp(COM_MCR_1,0xB);	/* Raise DTR and OUT2 */
			outp(COM_IER_1,1);	/* Interrupt enable register*/
			ch = inp(PIC_MASK);	/* Read the current 8259A
						   interrupt mask */
			ch &=  (0xFF^INT_MASK_1);/* Reset mask for COM1 */
			outp(PIC_MASK,ch);	/* Send it to the 8259A */
			break;
		}
		case 1:{
			asc_in_2 = asc_old_2 = 0;
			if((c2_buf = (unsigned char *)malloc(4096)) == NULL) {
				perror("Out of memory");
				exit(0);
			}
			old_c2 = _dos_getvect(COM_INT_2);  /* Be a friendly
							      ISR and save the
							      old vector for
							      restoration */
			_dos_setvect(COM_INT_2,int_com2);  /* Tell DOS whose
							      got the vector
							      now */
			outp(COM_MCR_2,0xB);	/* Raise DTR and OUT2 */
			outp(COM_IER_2,1);	/* Interrupt enable register*/
			ch = inp(PIC_MASK);	/* Read the current 8259A
						   interrupt mask */
			ch &=  (0xFF^INT_MASK_2);/* Reset mask for COM2 */
			outp(PIC_MASK,ch);	/* Send it to the 8259A */
			break;
		}
	}
}
void asc_disab(com_port)
int com_port;
{
	char ch;
	switch(com_port){
		case 0: {
			free(c1_buf);		/* Free the ring buffer */
			ch = inp(PIC_MASK);	/* Get 8259A (PIC) Mask */
			ch |= INT_MASK_1;	/* Set Interrupt Mask COM1 */
			outp(PIC_MASK,ch);	/* Write int. mask to 8259A*/
			outp(COM_MCR_1,inp(COM_MCR_1)^0xB);/* Lower DTR and OUT2 */
			_dos_setvect(COM_INT_1,old_c1);/* Return the vector
							  to its old position*/
			break;
		}
		case 1: {
			free(c2_buf);		/*Free the ring buffer */
			ch = inp(PIC_MASK);	/* Get 8259A (PIC) Mask */
			ch |= INT_MASK_2;	/* Set Interrupt Mask COM1 */
			outp(PIC_MASK,ch);	/* Write int. mask to 8259A*/
			outp(COM_MCR_2,inp(COM_MCR_2)^0xB);/* Lower DTR and OUT2 */
			_dos_setvect(COM_INT_2,old_c2);/* Restore the old
							  vector */
			break;
		}
	}
}

putcom(p,ch)
int p;
unsigned char ch;
{
	unsigned char status;
	switch(p) {
		case 0:{
			do {
				status = inp(COM_STAT_1);
			} while (!(status&0x20));
			outp(COM_DATA_1,ch);
			break;
		}
		case 1:{
			do {
				status = inp(COM_STAT_2);
			} while (!(status&0x20));
			outp(COM_DATA_2,ch);
			break;
		}
	}
}



-- 
____________________________________________________________________________
    My opinions are my own no matter	|	Robert W. McGwier, N4HY
    who I work for! ;-)			|	CCR, AMSAT, etc.
----------------------------------------------------------------------------

ralf@b.gp.cs.cmu.edu (Ralf Brown) (03/03/90)

In article <636@idacrd.UUCP> mac@idacrd.UUCP (Robert McGwier) writes:
}Microsoft C lets you define ISR's.  Here's code to do what you want if you
}decide that Microsoft might do the job.  I understand that Turbo C has
}copied this function type.  asc_enab enables the ISR, asc_disab disables that

That's a little hard to do, considering that TC had the "interrupt" function
type *before* MSC....
-- 
{backbone}!cs.cmu.edu!ralf   ARPA: RALF@CS.CMU.EDU   FIDO: Ralf Brown 1:129/46
BITnet: RALF%CS.CMU.EDU@CMUCCVMA   AT&Tnet: (412)268-3053 (school)   FAX: ask
DISCLAIMER? | _How_to_Prove_It_ by Dana Angluin  3. by vigorous handwaving:
What's that?|   	Works well in a classroom or seminar setting.