record - An application for capturing data from an HP CMS (Merlin) monitor 1.0.0
(13,506 bytes)
/* 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 <mecif.h>) 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 <mecif.h>) 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);
}