/* COMMINT.C: Interrupt driven comm port handler, written 100% in C. * Contains: All PC Hardware specific routines, all routines that * access the infifo data structure directly. * Goal: Provide high speed, publishable comm routines for state * machine driven Comm protocol series of articles. Should be usable * on either com1 or com2, but not both simultaneously. * Warning: This code deals with PC hardware directly, and should * only be used on 100% hardware compatibles. Also, if your program * aborts abnormally without removing the IH, I'm not responsible. * Externally callable routines include: * int Inst_IH( void interrupt (*faddr)(), int comnum) - Installs IH. * int Remove_IH() - Removes previously installed Int. handler * void Config_Comm( int port, S_INIT conf ) - Configure comm port * int incomm() - tests if a char. is available in the comm buffer * unsigned int getcomch() - returns oldest char. from comm buffer * int writecomm(unsigned char *buf, int len) - send chars via UART. */ #include #include "commint.h" /* Hardware specific (low level) defines */ #include "commn.h" /* common to commint and cterm files */ /* ------- Storage area to be able to put things back when we leave */ static void interrupt (*oldvect)(); static unsigned char oldimr; /* 8259 interrupt mask reg. */ static unsigned char old_Int_Enable; /* 8250 interrupt enable reg */ static S_INIT old_bps_lctrl; /* Saved speed, par, etc. */ /* ------- Globally visible variables and tables ----------- */ int CurPortAddr; /* Save the currently active comm port here */ unsigned char RxErr = 0; /* Note flags of comm read error */ unsigned int RxErrLoc; /* Save location of the first error */ /* ------- Preloaded arrays for Comm port configuration speed variables */ unsigned divisors[] = { 0x900, 0x600, 0x417, 0x359, 0x300, 0x180, 0x0C0, 0x060, 0x040, 0x03A, 0x030, 0x020, 0x018, 0x010, 0x00C, 0x006, 0x004, 0x003, 0x002, 0 }; unsigned speedi[] = { 50 , 75 , 110 , 135, 150 , 300 , 600 , 1200, 1800 , 2000 , 2400 , 3600 , 4800 , 7200 , 9600 ,19200, 28800, 38400, 57600, 0 }; /* ------- The input fifo (initialized) -------- */ static FIFO infifo = { 0, /* empty */ &infifo.data[0], /* init to start */ &infifo.data[0] }; static int inum = 0; /* used to tell if IH already installed */ /* External variables affected by this module */ extern S_INIT cur_config; /* from module CTERMx.C */ /* ---------- RXRDY interrupt handler ----------------------- * Does: Inputs serial data into receive FIFO. Overwrites if need be. * Pass: Nothing. * Sets: Global flag word as follows: Bits 0 :3 Line Status bits. * Bit 0: FIFO oflow, 1: UART Orun, 2: Parity Err, 3: Framing Err * Bits 4 :15 buf loc. of error. * NOTE: Only sets location area upon first error. User should clear. */ void interrupt rxrdy_IH() { unsigned char lstat; /* Used to save Line status locally */ lstat = inportb( CurPortAddr + LineStatus ); /* Insure there is data to receive.. */ if ( (lstat & DataRdyBit) != 0 ) { /* Bit zero on = data. */ *(infifo.hdr.inptr++) = inportb(CurPortAddr); /* read it and bump ptr. */ if (infifo.hdr.inptr == &infifo.data[FIFOSIZE]) infifo.hdr.inptr = &infifo.data[0]; /* wrap the circular buffer */ if ( ++infifo.hdr.count > FIFOSIZE ) { /* If data overwritten. */ /* Note that LSB is already set (DataRdyBit) and means buf overwrite */ if (RxErr == 0) { /* No Previous error? */ RxErr = lstat; RxErrLoc = infifo.hdr.inptr - &infifo.data[0]; } else RxErr |= lstat; /* OR Rcv errors as we go */ } else if ( (lstat &= 0x0E) != 0 ) /* Other Rcv errors? */ RxErr |= lstat; /* OR errors as we go */ } outportb(Int_Cmd_Reg, End_Int_Cmd); /* Tell 8259 we're done */ } /* ************** High level interface to hardware ********** */ /* ---------- Get_Conf ---------------------------------- * Does: Reads the speed, parity, stops of async UART. * Our interrupt handler need not be installed to do so. * Pass: The comm port # (1 or 2) of interest. * Returns: A struct containing S_INIT config info. */ S_INIT Get_Conf( int port ) { S_INIT conf; /* to be returned after filling in */ unsigned int divword; /* two halves of baud rate divisor */ int portaddr = (port == 1) ? COM1 : COM2; unsigned char line_ctrl, temp; int i; line_ctrl = inportb(portaddr + LineControl); /* Read current value */ /* Enable access to baud rate divisor regs */ outportb( (portaddr + LineControl), (line_ctrl | DivBit) ); divword = inportb(portaddr + DLL); /* Try the LSByte first */ temp = inportb(portaddr + 1); divword += (unsigned int) ( temp << 8 ); for ( i = 0; divisors[i] != 0; i++) /* look up divisor inlist */ if (divword == divisors[i]) break; conf.speed = speedi[i]; outportb( (portaddr + LineControl), line_ctrl ); /* See Data regs again */ conf.ubits.lctrl = line_ctrl; return(conf); } /* ---------- Config_Comm ---------------------------------- * Does: Configures speed, parity, stops of async port. * Our interrupt handler need not be installed to do so. * Pass: The comm port # (1 or 2) of interest. * A struct containing the desired config info. * Returns: NOTHING. * Use: Should be used once during init and every time config changes. * NOTE: Sets global cur_config to new value. */ void Config_Comm( int port, S_INIT conf ) { unsigned char divhi, divlo; /* two halves of baud rate divisor */ int portaddr = (port == 1) ? COM1 : COM2; int i; for ( i = 0; speedi[i] != 0; i++) /* look up the chosen speed */ if (conf.speed == speedi[i]) break; divhi = (unsigned char) (divisors[i] >> 8); divlo = (unsigned char) (divisors[i] & 0x00FF); outportb( (portaddr + LineControl), 0x80); /* To config baud divisors */ outportb( (portaddr + DLM), divhi); outportb( (portaddr + DLL), divlo); outportb( (portaddr + LineControl), conf.ubits.lctrl ); cur_config = conf; delay(100); } /* ---------- xmit_break ---------------------------------- * Does: Sends a 12 char time break signal to current comm port via UART. * Our interrupt handler need not be installed to do so. * Returns: nothing of value. */ int xmit_break( ) { unsigned char line_ctrl; long break_bits = ( 12 * 10 * 1000L); /* Bits per 12 char * 1000 */ int msecs; /* Number of msecs to send break */ msecs = (int)( break_bits / (long)cur_config.speed ); line_ctrl = inportb(CurPortAddr + LineControl); /* Read current value */ outportb( (CurPortAddr + LineControl), (line_ctrl | BrkBit) ); delay( msecs + 10 ); /* Add some for the timer interrupt */ outportb( (CurPortAddr + LineControl), line_ctrl ); return(0); } /* ---------- Install a comm interrupt handler --------- * Does: Installs the rxrdy comm interrupt handler requested * Pass: The address of the interrupt function. * Which comm port (1 or 2) you would like installed. * Uses: Global variable oldvect to save previous owner * Global CurPortAddr so that interrupt handler can use it. * Returns: 0 if alls well, or interrupt # already installed. */ int Inst_IH( void interrupt (*faddr)(), int comnum) { unsigned char temp; int i; unsigned long myvect; if (inum != 0) /* Do NOT install over ourselves */ return(inum); /* Both the below set globals for later use to disable */ inum = (comnum == 1) ? 0x0C : 0x0B; CurPortAddr = (comnum == 1) ? COM1 : COM2; disable(); /* Disable interrupts to change vectors */ oldvect = getvect(inum); /* Save old vector to clean up later */ old_Int_Enable = inportb(CurPortAddr + Int_Enable_Reg); /* Save this too */ /* Clear the 8250 UART of any garbage by reading all registers */ for (i = 0; i < 6; i++) temp = inportb(CurPortAddr + i); old_bps_lctrl = Get_Conf(comnum); /* Save UART speed, etc. */ /* Read the 8259 int mask reg and set IRQ3 or 4 low to enable interrupts. * Write result back to 8259 when finished */ oldimr = temp = inportb(Int_Mask_Reg); temp &= ( (CurPortAddr == COM1) ? 0xEF : 0xF7); outportb(Int_Mask_Reg, temp); /* Set Out2, DTR and RTS high for the 8250 */ temp = (Out2Bit + DTRBit + RTSBit); outportb( (CurPortAddr + ModemControl), temp); /* Set only rxrdy interrupt bit on (bit 0) */ outportb( (CurPortAddr + Int_Enable_Reg), 0x01 ); /* Finally set up the vector and wait and enable interupts */ setvect(inum, faddr); enable(); return(0); } /* ---------- Remove Interrupt Handler. ------------------ * Does: Removes an interrupt handler installed by Inst_IH. * Re-installs the saved 8259 and 8250 interrupt control regs. * Uses: Global variables oldvect, CurPortAddr, oldimr and old_Int_Enable. * Sets: Global inum to 0 if successfully removed. * Returns: 0 if alls well, or interrupt # already installed. */ int Remove_IH() { if (inum == 0) return(inum); disable(); /* Turn interrupts off to change vectors */ Config_Comm( (inum == 0x0C ? 1 : 2), old_bps_lctrl ); /* restore speed.. */ /* Restore 8259 and 8250 interrupt registers as they were */ outportb(Int_Mask_Reg, oldimr); outportb( (CurPortAddr + Int_Enable_Reg), old_Int_Enable); setvect(inum, oldvect); /* Put the other guys interrupt vector back */ inum = 0; /* reset to enable re-install */ enable(); return(0); } /* ---------- writecomm ------------------------- * Does: Writes a list of characters to the UART directly. * Tests UART xmit status. If full, waits until empty. * If full for too long, returns error value. * Pass: unsigned char pointer to start of buffer to send. * length of buffer to send. * Returns: 1 if write timeout or error, else returns 0. * NOTE: Not interrupt driven at this time. */ writecomm(unsigned char *buf, int len) { int i = len; int ctimes = 0; unsigned char *cptr = buf; while (i > 0) { if ( inportb(CurPortAddr + LineStatus) & TxRdyBit ) { outportb( CurPortAddr, *(cptr++) ); i--; ctimes = 0; } else { if (++ctimes > 1000) /* give it a good second to clear */ return(1); delay(1); /* should take 83 ms per ch at 1200, 333 at 300 */ } } return(0); } /* ********* Routines that access the infifo data structure ********** */ /* ---------- incomm ---------------------------------- * Does: Tests if a character is ready in the comm buffer. * Returns: The number of characters stored in the input fifo. * Use: Similar to keyboard inkey() call. */ int incomm() { return(infifo.hdr.count); } /* ---------- getcomch ---------------------------------- * Does: Returns the oldest character from the comm buffer. * Similar to keyboard getch() call. * WARNING: Don't call unless incomm() says it's O.K. * New 12/23: Now returns unsigned int. If RxErr is set, encodes * error information in the high byte from RxErr and zeroes RxErr. */ unsigned char getcomch() { unsigned char outch; outch = *(infifo.hdr.outptr++); /* if (RxErr != 0) * Pass up error and reset RxErr */ if (infifo.hdr.outptr == &infifo.data[FIFOSIZE]) /* wrap time? */ infifo.hdr.outptr = &infifo.data[0]; infifo.hdr.count--; return(outch); } /* ---------- eat_noise ------------------------ * Does: Digests any junk that has arrived in the buffer before it * is allowed to be interpreted by zeroing fifo counters and ptrs. * Use: Before initial NAK and just after send completes. * Returns: nothing * NOTE: Belongs in this module since this is the only place the infifo * structure is visible. */ void eat_noise() { infifo.hdr.count = 0; infifo.hdr.inptr = &infifo.data[0]; infifo.hdr.outptr = infifo.hdr.inptr; }