/* file: rs232g.c G. Moody 1 March 1994 Last revised: 9 March 1994 Replacement for rs232.c in MECIF library (uses Greenleaf CommLib) Copyright (C) 1994 by George B. Moody. Permission is granted to reproduce and distribute copies of this file freely provided that this copyright notice and permission statement are attached to all copies. About this file =============== This file contains definitions for the following functions: mecif_init to open a serial port at a desired speed mecif_end to close a previously opened serial port write_msg to write a complete message to an open port read_msg to read a complete message from an open port, if one is available These four functions replace those of the same names in `rs232.c' as supplied with the MECIF library, available as Part No. M1046-9220C (Dec. 1992) from Hewlett-Packard. The MECIF library includes a manual titled "HP Component Monitoring System RS-232 Computer Interface Programming Guide (Option #J13)", referred to below as the HP Guide. These functions are intended to be used with Greenleaf CommLib version 4.0 (available from Greenleaf Software Inc., 16479 Dallas Parkway, Suite 570, Dallas, TX 75248 USA; telephone +1 214 248 2561). CommLib is supplied in source form compatible with most popular C compilers, including those from Microsoft and Borland. The CommLib library includes a manual titled "Greenleaf CommLib 4.0 Reference", referred to below as the CommLib manual. Although these functions include more thorough checking of their inputs than their MECIF counterparts, except for read_msg they are not much more than wrappers for the CommLib functions that do the real work. Other functions defined in `rs232.c' are local to that module and are not needed in this implementation. The advantage of using this replacement for `rs232.c' is that the CommLib functions, unlike their MECIF counterparts, can exploit available hardware, including FIFO-equipped and `smart' serial cards, thereby permitting reliable operation at top speed concurrently with disk and keyboard interrupts. Reading and writing are non-blocking operations that read from and write to queues. The input queues (one for each open port) used by read_msg are filled in the background by CommLib interrupt service routines as characters arrive. read_msg copies as many characters as are available in the current message from the queue to its message buffer, and returns immediately; it returns NULL unless its message buffer contains a complete message. Similarly, write_msg simply copies bytes (with a little reformatting, see below) from its message buffer into its output queue, and returns immediately; the output queues are emptied in the background by CommLib interrupt service routines as rapidly as the ports can accept them. By default, these functions work with the standard PC serial ports, but if the symbol DIGIBOARD is defined at compilation time, they support the DigiBoard PC/4e (or PC/8i, etc.) `smart' cards. If a DigiBoard PC/4e is installed, follow the recommendations in the CommLib manual for setting it up. In summary, these are: - Be sure that the DigiBoard driver, xidos5.sys version 4.0.5 or later, is loaded. (Get the latest version from the DigiBoard BBS, +1 612 943 0812.) - Set up the DigiBoard driver to start defining ports at COM5 (this is the default setting). - Enable EBIOS support and disable the IRQ line and handshaking by the driver. See the CommLib manual for information about other supported serial boards. In principle, it should be possible to add support for any other CommLib-supported board simply by adding appropriate definitions for `port_open' and `portmap' below and recompiling. To use this file, simply copy it over the original `rs232.c', make sure that `commlib.h' is accessible to your C compiler via `#include "commlib.h"', rebuild the MECIF library (defining DIGIBOARD if appropriate), and link the rebuilt MECIF library and the CommLib library with your application program. About HP CMS message format =========================== The low-level format of HP CMS messages is incompletely described on page 3-3 of the HP Guide. With additional information gleaned from the code in the original version of rs232.c supplied with the MECIF library, here are the format details that are relevant to this module: 1. Each message begins with a single SYNC_HD (0x1b, defined in ) byte. 2. The next two bytes of each message are the low and high bytes of the message length. The message length specifies the number of bytes in the message, including the two length bytes but not including the initial SYNC_HD. The message length must always be even, between MIN_MSG_LENGTH and MAX_MSG_LENGTH (8 and 518, defined in ) inclusive. For this reason, neither byte of the length may be a SYNC_HD (because the low byte can't be odd, and the high byte can't be greater than 2). 3. The body of the message follows the length bytes. If a byte within the body happens to be a SYNC_HD, the transmitter sends a 0xFF byte immediately after the SYNC_HD, and the receiver discards the 0xFF upon receipt. This 0xFF byte is not considered to be part of the message and is not counted in the message length. Neither the message strings passed to write_msg nor those returned by read_msg contain these extra 0xFF bytes. The version of `rs232.c' supplied with the MECIF library treats the four bytes following the length bytes as part of the message body (i.e., it discards 0xff bytes following SYNC_HD bytes); according to the figure on page 3-3 of the HP Guide, these bytes, together with the length bytes, form the `transport header' and should not be treated as part of the message body. The functions in this file follow the policy of the MECIF library implementation, rather than that described in the HP Guide. The issue may be moot since the transport header apparently cannot contain any SYNC_HD bytes. About port numbers ================== To avoid having to make extensive modifications to the rest of the MECIF library, I have retained MECIF's port-numbering scheme here. MECIF ports are numbered 0, 1, 2, and 3 (only 0 and 1 are fully supported), corresponding in the standard case to the PC's COM1, COM2, COM3, and COM4 serial ports respectively. Each of the functions defined below accepts a MECIF port number as one of its inputs. There is no overhead associated with this, since the MECIF port number is used only to index an array of pointers to CommLib PORT structures. The `portmap' array (below) converts MECIF port numbers (0-3) into CommLib port numbers. The first map is for use with the PC's standard ports, and the second is for use with a DigiBoard PC/4e (or PC/8i, etc.) smart multiport board set up as COM5 through COM8. The portmap does not have to contain consecutive COM numbers. The CMS monitor supports a maximum of 4 lines; if this ever changes, simply change NPORTS and add additional definitions to portmap. Note that only `mecif_init' uses the portmap. */ #include "commlib.h" /* Greenleaf CommLib constants, macros, prototypes */ #include "meciflib.h" /* MECIF constants, etc. */ #define DIGIBOARD #define NPORTS 4 /* Maximum number of ports */ #ifndef DIGIBOARD #define port_open PortOpenGreenleafFast static int portmap[NPORTS] = { 0 /* COM1, see commlib.h */, COM2, COM3, COM4 }; #else #define port_open PortOpenSmartDigiboard static int portmap[NPORTS] = { COM5, COM6, COM7, COM8 }; #endif static PORT *port[NPORTS]; /* pointers to CommLib port structures */ /* Initialize a port at the specified baud rate. */ i_16 mecif_init(u_16 portno, u_16 baud_rate) { if (portno >= NPORTS) return (PORT_WRONG); port[portno] = port_open(portmap[portno], (long)baud_rate, 'N', 8, 1); switch (port[portno]->status) { case ASSUCCESS: UseDtrDsr(port[portno], 1); return (SUCCESS); case ASNOMEMORY: return (NOT_ENOUGH_MEMORY); case ASILLEGALBAUDRATE: return (BAUDRATE_WRONG); default: return (FAIL); } } /* Close a previously opened port. */ void mecif_end(u_16 portno) { if (portno < NPORTS && port[portno]) { (void)PortClose(port[portno]); port[portno] = (PORT *)NULL; } } /* Write a message to an open port. */ i_16 write_msg(u_16 portno, u_16 *mptr) { char *ip, *op, *tp; int len; PORT *p; static char obuf[MAX_MSG_LENGTH*2]; if (portno >= NPORTS || (p = port[portno]) == NULL) return (FAIL); /* not an open port */ if (!mptr) return (FAIL); /* no message to be written */ if ((len = *mptr) < MIN_MSG_LENGTH || *mptr > MAX_MSG_LENGTH || (*mptr&1)) return (FAIL); /* illegal message length */ ip = (char *)mptr; /* byte pointer to beginning of message */ op = tp = obuf; *op++ = SYNC_HD; while (len-- > 0) if ((*op++ = *ip++) == SYNC_HD) *op++ = 0xff; while (op > tp && WriteBuffer(p, tp, op - tp) == ASBUFRFULL) tp += p->count; return (SUCCESS); } /* This local function uses the CommLib ReadBuffer function to read many characters at a time, if available, and to return them one at a time to the caller; its interface is similar to that of the CommLib ReadChar function. Using the standard CommLib driver, this simply introduces extra overhead (since the ReadBuffer implementation there uses ReadChar), but when using the CommLib smart DigiBoard driver, this method exploits the ability of the DigiBoard card to offload I/O processing from the CPU. */ #define MRCSIZE 512 /* max characters to be read at once */ static int MReadChar(u_16 portno) { static char ibuf[NPORTS][MRCSIZE], *ip[NPORTS]; static int icount[NPORTS]; if (icount[portno] == 0) { PORT *p = port[portno]; (void)ReadBuffer(p, ibuf[portno], MRCSIZE); if (p->count == 0) return (ASBUFREMPTY); icount[portno] = p->count; ip[portno] = ibuf[portno]; } icount[portno]--; return (*(ip[portno]++) & 0xff); } /* Read a message from an open port, if available. If a complete message is available, this function returns a pointer to it; otherwise, it returns NULL. Note that if read_msg detects that one or more bytes have been lost from a given port, it defers reporting this (via rx_error) until a complete message (with no lost bytes) has been received from the port. This behavior is consistent with that of the read_msg implementation in the original rs232.c. The message reader is implemented as a 5-state finite state machine (FSM) for each port. The initial state of each FSM is WAIT_FOR_SYNC (defined below). The `while' loop reads a character from the input queue on each iteration, and exits at the end of this function if the input queue is empty. Once it has been set (just before entering the `while' loop), `mp' points to the structure containing the message buffer and other variables associated with the selected port. Within the loop, `c' is the character that has just been read. */ /* States of the message-reader FSMs. */ #define WAIT_FOR_SYNC 0 /* <-- don't change this one! */ #define WAIT_FOR_0XFF 1 #define WAIT_FOR_LLEN 2 #define WAIT_FOR_HLEN 3 #define READING_BODY 4 u_16 *read_msg(u_16 portno) { int c, len; static struct rmstruct { char mbuf[MAX_MSG_LENGTH+2]; /* message buffer */ char *bp; /* pointer to next available position in mbuf */ char *ep; /* pointer to (expected) end of message */ char lost; /* FALSE: no bytes lost since the previous message TRUE: one or more bytes lost */ char state; /* FSM state */ } m[NPORTS]; struct rmstruct *mp; if (portno >= NPORTS || port[portno] == NULL) return (NULL); /* not an open port */ mp = &m[portno]; while ((c = MReadChar(portno)) != ASBUFREMPTY) { switch (mp->state) { case WAIT_FOR_SYNC: if (c == SYNC_HD) mp->state = WAIT_FOR_LLEN; else mp->lost = TRUE; break; case WAIT_FOR_0XFF: if (c == 0xff) { mp->bp++; mp->state = READING_BODY; break; } mp->lost = TRUE; /* no `break' here: fall through to next case */ case WAIT_FOR_LLEN: if ((c & 1) == 0) { mp->bp = mp->mbuf; *(mp->bp++) = (char)c; mp->state = WAIT_FOR_HLEN; } else { mp->lost = TRUE; if (c != SYNC_HD) mp->state = WAIT_FOR_SYNC; } break; case WAIT_FOR_HLEN: *(mp->bp++) = (char)c; len = *((int *)mp->mbuf); if (c == SYNC_HD) { mp->lost = TRUE; mp->state = WAIT_FOR_LLEN; } else if (MIN_MSG_LENGTH <= len && len <= MAX_MSG_LENGTH) { mp->ep = mp->mbuf + len; mp->state = READING_BODY; break; } else { mp->lost = TRUE; mp->state = WAIT_FOR_SYNC; } break; case READING_BODY: *(mp->bp) = (char)c; if (c != SYNC_HD) mp->bp++; else mp->state = WAIT_FOR_0XFF; break; } if (mp->state == READING_BODY && mp->bp >= mp->ep) { if (mp->lost) { int i; extern Session *session_ptr[]; Session **sp = session_ptr; for (i = 0; i < MAX_SESSIONS; i++, sp++) if (*sp && (*sp)->port_id == portno) rx_error(MESSAGE_LOST, (*sp)->con_id); mp->lost = FALSE; } mp->state = WAIT_FOR_SYNC; return ((u_16 *)mp->mbuf); } } return (NULL); }