/*
 * Copyright 1989, 1990, 1995, 1997 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

/*
 * This file is an ugly mess of UNIX specific code portable Mac-side code.
 * The UNIX specific stuff should be put in its appropriate place sometime.
 */


#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_serial[] =
		"$Id: serial.c 88 2005-05-25 03:59:37Z ctm $";
#endif

#include "rsys/common.h"
#include "Serial.h"
#include "DeviceMgr.h"
#include "FileMgr.h"
#include "MemoryMgr.h"
#include "OSUtil.h"

#include "rsys/file.h"
#include "rsys/hfs.h"

#if defined (MSDOS)
#include "dosserial.h"
#endif

#if defined (CYGWIN32)
#include "win_serial.h"
#endif

#if !defined (CYGWIN32)
#include <termios.h>
#endif

#if defined (LINUX)
#include <sys/ioctl.h>
#endif

#if defined (__alpha) || defined (LINUX)
#define TERMIO
/* #define	MSDOS	ick! */
#endif

/*
 * TODO:
 *
 * Post events on break action or hardware handshake changes IMII-247
 * Support KillIO from the device manager IMII-248
 * Hardware handshaking on machines that support it IMII-252
 *
 * Finish up the get status routine?
 */

#define AINNAME		"\004.AIn"
#define AOUTNAME	"\005.AOut"
#define BINNAME		"\004.BIn"
#define BOUTNAME	"\005.BOut"
#define AINREFNUM	(-6)
#define AOUTREFNUM	(-7)
#define BINREFNUM	(-8)
#define BOUTREFNUM	(-9)

#if !defined(XONC)
#define XONC		'\21'
#define XOFFC		'\23'
#endif

#define SER_START	(1)
#define SER_STOP	(0)

A1(PUBLIC, OSErr, RAMSDOpen, SPortSel, port)	/* IMII-249 */
{
    OSErr err;
    INTEGER rn;

    switch (port) {
    case sPortA:
	if ((err = OpenDriver((StringPtr) AINNAME, &rn)) == noErr)
	    err = OpenDriver((StringPtr) AOUTNAME, &rn);
	break;
    case sPortB:
	if ((err = OpenDriver((StringPtr) BINNAME, &rn)) == noErr)
	    err = OpenDriver((StringPtr) BOUTNAME, &rn);
	break;
    default:
	err = openErr;
	break;
    }
    return err;
}

A1(PUBLIC, void, RAMSDClose, SPortSel, port)	/* IMII-250 */
{
    switch (port) {
    case sPortA:
	(void) CloseDriver(AINREFNUM);
	(void) CloseDriver(AOUTREFNUM);
	break;
    case sPortB:
	(void) CloseDriver(BINREFNUM);
	(void) CloseDriver(BOUTREFNUM);
	break;
    }
}

#define SERSET	8	/* IMII-250 */

A2(PUBLIC, OSErr, SerReset, INTEGER, rn, INTEGER, config)	/* IMII-250 */
{
  config = CW (config);

  return Control(rn, SERSET, (Ptr) &config);
}

#define SERSETBUF	9	/* IMII-251 */

A3(PUBLIC, OSErr, SerSetBuf, INTEGER, rn, Ptr, p, INTEGER, len)	/* IMII-251 */
{
    struct {
	Ptr p;
	INTEGER i;
    } temp;

    temp.p = RM (p);
    temp.i = CW (len);

    return Control(rn, SERSETBUF, (Ptr) &temp);
}

#define SERHSHAKE	10	/* IMII-251 */

A2(PUBLIC, OSErr, SerHShake, INTEGER, rn, SerShk, flags)	/* IMII-251 */
{
    return Control(rn, SERHSHAKE, (Ptr) &flags);
}

#define SERSETBRK	12	/* IMII-252 */

A1(PUBLIC, OSErr, SerSetBrk, INTEGER, rn)			/* IMII-252 */
{
    return Control(rn, SERSETBRK, (Ptr) 0);
}

#define SERCLRBRK	11	/* IMII-253 */

A1(PUBLIC, OSErr, SerClrBrk, INTEGER, rn)			/* IMII-253 */
{
    return Control(rn, SERCLRBRK, (Ptr) 0);
}

#define SERGETBUF	2	/* IMII-253 */

A2(PUBLIC, OSErr, SerGetBuf, INTEGER, rn, LONGINT *, lp)	/* IMII-253 */
{
    INTEGER status[11];
    OSErr err;

    if ((err = Status(rn, SERGETBUF, (Ptr) status)) == noErr)
	*lp = *(LONGINT *)status;
    return err;
}

#define SERSTATUS	8	/* IMII-253 */

A2(PUBLIC, OSErr, SerStatus, INTEGER, rn, SerStaRec *, serstap)	/* IMII-253 */
{
    INTEGER status[11];
    OSErr err;

    if ((err = Status(rn, SERSTATUS, (Ptr) status)) == noErr)
	BlockMove((Ptr) status, (Ptr) serstap, (Size) sizeof(*serstap));
    return err;
}

#define OPENBIT	(1 << 0)

#if !defined(MSDOS) && !defined (CYGWIN32)
#if defined(TERMIO)

typedef struct {
    LONGINT fd;		/* will be the same for both In and Out */
    struct termio state;
} hidden, *hiddenp;

#else

typedef struct {
    LONGINT fd;		/* will be the same for both In and Out */
    struct sgttyb sgttyb;
    struct tchars tchars;
    ULONGINT lclmode;
} hidden, *hiddenp;

#endif
#else

typedef struct
{
  LONGINT fd; /* probably need to save more state information */
  /* right now we just use fd to keep track of which port we're talking to */
}
hidden, *hiddenp;

#endif

typedef struct { hiddenp p PACKED_P; } HIDDEN_hiddenp;
typedef HIDDEN_hiddenp *hiddenh;


A1(PRIVATE, DCtlPtr, otherdctl, ParmBlkPtr, pbp)
{
    DCtlHandle h;

    h = 0;
    switch (CW(pbp->cntrlParam.ioCRefNum)) {
    case AINREFNUM:
	h = GetDCtlEntry(AOUTREFNUM);
	break;
    case AOUTREFNUM:
	h = GetDCtlEntry(AINREFNUM);
	break;
    case BINREFNUM:
	h = GetDCtlEntry(BOUTREFNUM);
	break;
    case BOUTREFNUM:
	h = GetDCtlEntry(BINREFNUM);
	break;
    }
    return h ? STARH(h) : 0;
}

#if defined (LINUX) || defined (NEXTSTEP)

/*
 * NOTE:  Currently we're using cufa and cufb; we really should
 *	  delay the opening until we know just what type of flow
 *	  control the user wants to do.
 */
PRIVATE char *specialname(ParmBlkPtr pbp, const char **lockfilep,
						        const char **tempfilep)
{
    char *retval;

    switch (CW(pbp->cntrlParam.ioCRefNum)) {
    case AINREFNUM:
    case AOUTREFNUM:
#if defined (NEXTSTEP)
	retval = "/dev/cufa";
	*lockfilep = "/usr/spool/uucp/LCK/LCK..cufa";
	*tempfilep = "/usr/spool/uucp/LCK/etempcufa";
#elif defined (LINUX)
	retval = "/dev/cua0";
	*lockfilep = 0; /* for now */
	*tempfilep = 0; /* for now */
#endif
	break;
    case BINREFNUM:
    case BOUTREFNUM:
#if defined (NEXTSTEP)
	retval = "/dev/cufb";
	*lockfilep = "/usr/spool/uucp/LCK/LCK..cufb";
	*tempfilep = "/usr/spool/uucp/LCK/etempcufa";
#elif defined (LINUX)
	retval = "/dev/cua1";
	*lockfilep = 0;
	*tempfilep = 0;
#endif
	break;
    default:
	*lockfilep = 0;
	retval = 0;
    }
    return retval;
}
#endif /* defined (LINUX) || defined (NEXTSTEP) */

typedef void (*compfuncp)( void );

#if defined(BINCOMPAT)

void callcomp(ParmBlkPtr pbp, compfuncp comp, OSErr err)
{
    EM_A0 = (LONGINT) (long) US_TO_SYN68K(pbp);
    EM_A1 = (LONGINT) (long) US_TO_SYN68K(comp);
    EM_D0 = (unsigned short) err;	/* TODO: unsigned short ? */
    CALL_EMULATOR((syn68k_addr_t) (long) comp);
}

#define DOCOMPLETION(pbp, err)						      \
    (pbp)->ioParam.ioResult = CW(err);					      \
    if (((pbp)->ioParam.ioTrap & asyncTrpBit) && (pbp)->ioParam.ioCompletion) \
	callcomp(pbp, (compfuncp) CL((long)(pbp)->ioParam.ioCompletion), err);	      \
    return err

#else

#warning BINCOMPAT not defined

#define DOCOMPLETION(pbp, err)				\
    (pbp)->ioParam.ioResult = CW(err);			\
    if ((pbp)->ioParam.ioTrap & asyncTrpBit)		\
	(*(compfuncp) (pbp)->ioParam.ioCompletion)();	\
    return err

#endif

#define SERIALDEBUG

#if defined (LINUX) || defined (NEXTSTEP)
PRIVATE const char *lockname;
#endif

A2(PUBLIC, OSErr, ROMlib_serialopen, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    auto DCtlPtr otherp;	/* auto due to old compiler bug */
    hiddenh h;
#if defined (LINUX) || defined (NEXTSTEP)
    const char *devname, *tempname;
    LONGINT fd, ourpid, theirpid, newfd, oumask;
#endif

    err = noErr;
    if (!(dcp->dCtlFlags & CWC(OPENBIT))) {
	h = (hiddenh) NewHandle(sizeof(hidden));
	dcp->dCtlStorage = (Handle) RM(h);
	otherp = otherdctl(pbp);
	if (otherp && (otherp->dCtlFlags & CWC(OPENBIT))) {
	    *STARH(h) = *STARH((hiddenh) (long) MR(otherp->dCtlStorage));
	    dcp->dCtlFlags |= CWC(OPENBIT);
	} else {
#if defined (LINUX) || defined (NEXTSTEP)
	    err = permErr;
	    if ((devname = specialname(pbp, &lockname, &tempname))) {
		oumask = umask(0);
		if (!tempname)
		  err = noErr;
		else if ((fd = Uopen(tempname, O_BINARY|O_CREAT|O_WRONLY, 0666L))
									 >= 0) {
		    ourpid = getpid();
		    if (write(fd, &ourpid, sizeof(ourpid)) == sizeof(ourpid)) {
			if (Ulink(tempname, lockname) == 0)
			    err = noErr;
			else {
			    if ((newfd = Uopen(lockname, O_BINARY|O_RDWR, 0))
									>= 0) {
				if (read(newfd, &theirpid, sizeof(theirpid))
							   == sizeof(theirpid))
				    if ((kill(theirpid, 0) != 0) &&
							      errno == ESRCH) {
					err = noErr;
					Uunlink(lockname);
					Ulink(tempname, lockname);
				    }
				close(newfd);
			    }
			}
			Uunlink(tempname);
		    }
		    close(fd);
		}
		umask(oumask);
	    }
#endif
	    if (err == noErr) {
#if defined (LINUX) || defined (NEXTSTEP)
		HxX(h, fd) = ROMlib_priv_open(devname, O_BINARY|O_RDWR);
		if (HxX(h, fd) < 0) 
		    err = HxX(h, fd);	/* error return piggybacked */
		else {
#if defined(TERMIO)
		    err = ioctl(HxX(h, fd), TCGETA, &HxX(h, state)) < 0 ?
						     ROMlib_maperrno() : noErr;
#else
		    if (ioctl(HxX(h, fd), TIOCGETP, &HxX(h, sgttyb)) < 0 ||
			    ioctl(HxX(h, fd), TIOCGETC, &HxX(h, tchars)) < 0 ||
			     ioctl(HxX(h, fd), TIOCLGET, &HxX(h, lclmode)) < 0)
			err = ROMlib_maperrno();
#endif
#else
		    HxX(h, fd) = (CW(pbp->cntrlParam.ioCRefNum) == AINREFNUM ||
		      CW(pbp->cntrlParam.ioCRefNum) == AOUTREFNUM) ? 0 : 1;
#endif
		    dcp->dCtlFlags |= CWC(OPENBIT);
		    SerReset(CW(pbp->cntrlParam.ioCRefNum),
			    (CW(pbp->cntrlParam.ioCRefNum) == AINREFNUM ||
			     CW(pbp->cntrlParam.ioCRefNum) == AOUTREFNUM) ?
						    CW(SPPortA) : CW(SPPortB));
#if defined (LINUX) || defined (NEXTSTEP)
		}
#endif
	    }
	}
    }
#if defined(SERIALDEBUG)
    warning_trace_info("serial open returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}

A2(PUBLIC, OSErr, ROMlib_serialprime, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    hiddenh h;
    char *buf;
    size_t req_count;

    buf = (char *) MR (pbp->ioParam.ioBuffer);
    req_count = CL (pbp->ioParam.ioReqCount);

    err = noErr;
    if (!(dcp->dCtlFlags & CWC(OPENBIT)))
	err = notOpenErr;
    else {
#if defined(SERIALDEBUG)
    warning_trace_info ("serial prime code %d", (LONGINT) (CW(pbp->ioParam.ioTrap) & 0xFF));
#endif
	h = (hiddenh) MR(dcp->dCtlStorage);
	switch (CW(pbp->ioParam.ioTrap) & 0xFF) {
	case aRdCmd:
	    if (CW(pbp->cntrlParam.ioCRefNum) != AINREFNUM &&
				    CW(pbp->cntrlParam.ioCRefNum) != BINREFNUM)
		err = readErr;
	    else {
		/* this may have to be changed since we aren't looking for
		   parity and framing errors */
#if defined (LINUX) || defined (NEXTSTEP)
		pbp->ioParam.ioActCount = CL(read(HxX(h, fd), buf, req_count));
#elif defined (MSDOS) || defined (CYGWIN32)
		pbp->ioParam.ioActCount = CL(serial_bios_read(HxX(h, fd), buf,
							      req_count));
#else
#warning not sure what to do here
#endif
#if defined(SERIALDEBUG)
    warning_trace_info ("serial prime read %d bytes, first is 0x%0x",
					    (LONGINT) CL(pbp->ioParam.ioActCount),
			  (LONGINT) (unsigned char) buf[0]);
#endif
	    }
	    break;
	case aWrCmd:
	    if (CW(pbp->cntrlParam.ioCRefNum) != AOUTREFNUM &&
				       CW(pbp->cntrlParam.ioCRefNum) != BOUTREFNUM)
		err = writErr;
	    else {
#if defined(SERIALDEBUG)
    warning_trace_info ("serial prime writing %d bytes, first is 0x%0x",
					        (LONGINT) req_count,
			      (LONGINT) (unsigned char) buf[0]);
#endif
#if defined (LINUX) || defined (NEXTSTEP)
		pbp->ioParam.ioActCount = CL(write(HxX(h, fd),
			       buf, req_count));
#elif defined (MSDOS) || defined (CYGWIN32)
		pbp->ioParam.ioActCount = CL( serial_bios_write(HxX(h, fd),
			       buf, req_count));
#else
#warning not sure what to do here
#endif
	    }
	    break;
	default:
	    err = badUnitErr;
	    break;
	}
    }
#if defined(SERIALDEBUG)
    warning_trace_info ("serial prime returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}

#if defined(TERMIO)

#define OURCS7		(CS7)
#define OURCS8		(CS8)
#define OURODDP		(PARENB|PARODD)
#define OUREVENP	(PARENB)

#else

#define OURCS7		(0)

#if defined(NEXTSTEP)
#define OURCS8		(LPASS8|LPASS8OUT)
#else
#define OURCS8		(LPASS8)
#endif


#define OURODDP		(ODDP)
#define OUREVENP	(EVENP)

#endif

A2(PRIVATE, OSErr, serset, LONGINT, fd, INTEGER, param)
{
#if defined (MSDOS) || defined (CYGWIN32)
  OSErr retval;

  retval = serial_bios_serset (fd, param);
  return retval;
#else


#if defined(TERMIO)
    struct termio settings;
#else
    struct sgttyb sgttyb;
    struct tchars tchars;
    ULONGINT lclmode;
#endif

    OSErr err;
    LONGINT baud, csize, stopbits, parity;

#if !defined (LETGCCWAIL)
    baud = 0;
    csize = 0;
    stopbits = 0;
#endif

    err = noErr;

    switch (param & 0x3FF) {
    case baud300:
	baud = B300; 
	break;
    case baud600:
	baud = B600; 
	break;
    case baud1200:
	baud = B1200; 
	break;
    case baud1800:
	baud = B1800; 
	break;
    case baud2400:
	baud = B2400; 
	break;
    case baud4800:
	baud = B4800; 
	break;
    case baud9600:
	baud = B9600; 
	break;
    case baud19200:
	baud = EXTA; 
	break;
    case 1:		/* really 38400 according to Terminal 2.2 source */
	baud = EXTB;
	break;
    case baud3600:	/* these case labels aren't necessary since they */
    case baud7200:	/* fall into default anyway, just thought you */
    case baud57600:	/* might like to know what you're missing */
    default:
/*
 * NOTE: setting controlErr here isn't probably a good idea, since
 *       Compuserve Signup does a serset with baud57600 and then later
 *	 adjusts the baud with a "setbaud"
 */
#if 0
	err = controlErr;
#else
	baud = B2400;
#endif
	break;
    }

    switch (param & 0xC00) {

    case data5:
    case data6:
	err = controlErr;
	break;

    case data7:
	csize = OURCS7;
	break;

    case data8:
	csize = OURCS8;
	break;

    }

    switch (param & 0x3000) {

    case oddParity:
	parity = OURODDP;
	break;

    case evenParity:
	parity = OUREVENP;
	break;

    default:
	parity = 0;
	break;
    }

    switch ((param >> 14) & 0x3) {
    case 1:
	stopbits = 0;
	break;
    case 3:
#if defined(TERMIO)
	stopbits = CSTOPB;
	break;
#endif
    case 0:
    case 2:
	err = controlErr;
	break;
    }

    if (err == noErr) {
#if defined(TERMIO)
	settings.c_iflag = IGNPAR | IXON | IXOFF;
	settings.c_oflag = 0;
	settings.c_cflag = baud | csize | stopbits | parity;
	settings.c_lflag = 0;
	settings.c_line  = 0;	/* TODO: find out real value here */
	err = ioctl(fd, TCSETAW, &settings) < 0 ? ROMlib_maperrno() : noErr;
#else
	sgttyb.sg_ispeed = sgttyb.sg_ospeed = baud;
	sgttyb.sg_erase = -1;
	sgttyb.sg_kill = -1;
	sgttyb.sg_flags = TANDEM | CBREAK | parity;
	tchars.t_intrc = -1;
	tchars.t_quitc = -1;
#if 0 /* This isn't the place to mess with flow control */
	if (sershkp->fXOn || sershkp->fInX) {
	    tchars.t_startc = sershkp->xOn;
	    tchars.t_stopc = sershkp->xOff;
	} else {
	    tchars.t_startc = sershkp->xOn;
	    tchars.t_stopc = sershkp->xOff;
	}
	/* Cliff, LDECCTQ restricts XON to startc ONLY! --Lee */
	lclmode = sershkp->fXOn ? LDECCTQ : 0;
#else
	lclmode = 0;
#endif
	tchars.t_eofc = -1;
	tchars.t_brkc = -1;
	lclmode |= LLITOUT | LNOHANG | csize | LNOFLSH;
	if (ioctl(fd, TIOCSETP, &sgttyb) < 0 ||
					    ioctl(fd, TIOCSETC, &tchars) < 0 ||
					     ioctl(fd, TIOCLSET, &lclmode) < 0)
	    err = ROMlib_maperrno();
#endif
    }
    return err;
#endif
}

A2(PRIVATE, OSErr, serxhshake, LONGINT, fd, SerShk *, sershkp)
{
#if defined (MSDOS) || defined (CYGWIN32)
  OSErr retval;

  retval = serial_bios_serxhshake (fd, sershkp);
  return retval;
#else

#if defined(TERMIO)
    struct termio settings;
#else
    struct sgttyb sgttyb;
    struct tchars tchars;
#endif
    OSErr err;

    err = noErr;
#if defined(TERMIO)
    if (ioctl(fd, TCGETA, &settings) < 0)
	err = ROMlib_maperrno();
#else
    if (ioctl(fd, TIOCGETP, &sgttyb) < 0 || ioctl(fd, TIOCGETC, &tchars) < 0)
	    err = ROMlib_maperrno();
#endif
    else {
	if (sershkp->fXOn) {
#if defined(TERMIO)
	    settings.c_iflag |=  IXON;
	} else
	    settings.c_iflag &= ~IXON;
#else
	    /* NB: LDECCTQ is left (un)set in the local mode word. */
	    tchars.t_startc = -1;
	    tchars.t_stopc = -1;
	} else {
	    tchars.t_startc = sershkp->xOn;
	    tchars.t_stopc = sershkp->xOff;
	}
#endif
	if (sershkp->fInX) {
#if defined(TERMIO)
	    settings.c_iflag |=  IXOFF;
	} else
	    settings.c_iflag &= ~IXOFF;
	    err = ioctl(fd, TCSETAW, &settings) < 0 ? ROMlib_maperrno() : noErr;
#else
	    /* Can't enable TANDEM without setting startc & stopc. If I
	     * do that, input flow control is also enabled. This is what
	     * has been implemented here. The alternative is to refuse the
	     * request.
	     */
	    sgttyb.sg_flags |= TANDEM;
	    tchars.t_startc = sershkp->xOn;
	    tchars.t_stopc = sershkp->xOff;
	} else
	    sgttyb.sg_flags &= ~TANDEM;
	if (ioctl(fd, TIOCSETP, &sgttyb) < 0 ||
					      ioctl(fd, TIOCSETC, &tchars) < 0)
	    err = ROMlib_maperrno();
#endif
    }
    return err;
#endif
}

A2(PRIVATE, OSErr, setbaud, LONGINT, fd, INTEGER, baud)
{
#if defined (MSDOS) || defined (CYGWIN32)
  OSErr retval;

  retval = serial_bios_setbaud (fd, baud);
  return retval;
#else

#if defined(TERMIO)
    struct termio settings;
#else
    struct sgttyb sgttyb;
#endif
    static INTEGER rates[] = {
	     50,	  75,	 110,	 134,	 150,	 200,	  300,
	    600,	1200,	1800,	2400,	4800,	9600,	19200,
	  38400, 	32767,
    }, *ip;
    OSErr err;

    for (ip = rates; *ip < baud ; ip++)
	;
    if (*ip == 32767 || ip[0] - baud > baud - ip[-1])
	--ip;
#if defined(TERMIO)
    if (ioctl(fd, TCGETA, &settings) < 0)
#else
    if (ioctl(fd, TIOCGETP, &sgttyb) < 0)
#endif
	err = ROMlib_maperrno();
    else {
#if defined(TERMIO)
	settings.c_cflag &= ~CBAUD;
	settings.c_cflag |= ip - rates + 1;
	err = ioctl(fd, TCSETAW, &settings) < 0 ? ROMlib_maperrno() : noErr;
#else
	sgttyb.sg_ispeed = sgttyb.sg_ospeed = ip - rates + 1;
	err = ioctl(fd, TIOCGETP, &sgttyb) < 0 ? ROMlib_maperrno() : noErr;
#endif
    }
    return err;
#endif
}

A2(PRIVATE, OSErr, ctlbrk, LONGINT, fd, INTEGER, flag)
{
#if defined (MSDOS) || defined (CYGWIN32)
  OSErr retval;

  retval = serial_bios_ctlbrk (fd, flag);
  return retval;
#else
	OSErr err;

#if defined(TERMIO)
    if (flag == SER_START) {
	/* NOTE:  This will send a break for 1/4 sec */
	err = ioctl(fd, TCSBRK, 0);
    } else
	err = noErr;
#else
    err = ioctl(fd, flag == SER_START ? TIOCSBRK : TIOCCBRK, 0);
#endif
    return err < 0 ? ROMlib_maperrno() : noErr;
#endif
}

A2(PRIVATE, OSErr, flow, LONGINT, fd, LONGINT, flag)
{
#if defined (MSDOS) || defined (CYGWIN32)
  OSErr retval;

  retval = serial_bios_setflow (fd, flag);
  return retval;
#else
    OSErr err;

#if defined(TERMIO)
#if !defined(__alpha)
    err = ioctl(fd, TCXONC, flag == SER_STOP ? 0 : 1);
#else
    err = ioctl(fd, TCXONC, (void *)(flag == SER_STOP ? 0L : 1L));
#endif
#else
    err = ioctl(fd, flag == SER_STOP ? TIOCSTOP : TIOCSTART, 0);
#endif
    return err < 0 ? ROMlib_maperrno() : noErr;
#endif
}

#define SERBAUDRATE	13

#define SERXHSHAKE	14	/* IMIV-226 */
#define SERMISC		16	/* IMIV-226 */
#define SERSETDTR	17	/* IMIV-226 */
#define SERCLRDTR	18	/* IMIV-226 */

#define SERCHAR		19

#define SERXCHAR	20	/* IMIV-226 */

#define SERUSETXOFF	21
#define SERUCLRXOFF	22
#define SERCXMITXON	23
#define SERUXMITXON	24
#define SERCXMITXOFF	25
#define SERUXMITXOFF	26
#define SERRESET	27

#define SERKILLIO	 1

A2(PUBLIC, OSErr, ROMlib_serialctl, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    hiddenh h;
	char c;

    if (!(dcp->dCtlFlags & CWC(OPENBIT)))
	err = notOpenErr;
    else {
#if defined(SERIALDEBUG)
	warning_trace_info ("serial control code = %d, param0 = 0x%x",
					 (LONGINT) CW(pbp->cntrlParam.csCode),
		       (LONGINT) (unsigned short) CW(pbp->cntrlParam.csParam[0]));
#endif
	h = (hiddenh) MR(dcp->dCtlStorage);
	switch (CW(pbp->cntrlParam.csCode)) {
	case SERKILLIO:
	    err = noErr;	/* All I/O done synchronously */
	    break;
	case SERSET:
	    err = serset(HxX(h, fd), CW(pbp->cntrlParam.csParam[0]));
	    break;
	case SERSETBUF:
	    err = noErr;	/* ignored */
	    break;
	case SERHSHAKE:
	case SERXHSHAKE:	/* NOTE:  DTR handshake isn't supported  */
	    err = serxhshake(HxX(h, fd), (SerShk *) pbp->cntrlParam.csParam);
	    break;
	case SERSETBRK:
	    err = ctlbrk(HxX(h, fd), SER_START);
	    break;
	case SERCLRBRK:
	    err = ctlbrk(HxX(h, fd), SER_STOP);
	    break;
	case SERBAUDRATE:
	    err = setbaud(HxX(h, fd), CW(pbp->cntrlParam.csParam[0]));
	    break;
	case SERMISC:
#if defined (MSDOS) || defined (CYGWIN32)
	    err = serial_bios_setdtr (HxX(h, fd));
#else
	    err = controlErr;	/* not supported */
#endif
	    break;
	case SERSETDTR:
#if defined (MSDOS) || defined (CYGWIN32)
	    err = serial_bios_clrdtr (HxX(h, fd));
#else
	    err = controlErr;	/* not supported */
#endif
	    break;
	case SERCLRDTR:
	    err = controlErr;	/* not supported */
	    break;
	case SERCHAR:
	    err = controlErr;	/* not supported */
	    break;
	case SERXCHAR:
	    err = controlErr;	/* not supported */
	    break;
	case SERUSETXOFF:
	    err = flow(HxX(h, fd), SER_STOP);
	    break;
	case SERUCLRXOFF:
	    err = flow(HxX(h, fd), SER_START);
	    break;
	case SERCXMITXON:
	    err = controlErr;	/* not supported */
	    break;
	case SERUXMITXON:
	    c = XONC;
	    err = write(HxX(h, fd), &c, 1) != 1 ? ROMlib_maperrno() : noErr;
	    break;
	case SERCXMITXOFF:
	    err = controlErr;	/* not supported */
	    break;
	case SERUXMITXOFF:
	    c = XOFFC;
	    err = write(HxX(h, fd), &c, 1) != 1 ? ROMlib_maperrno() : noErr;
	    break;
	case SERRESET:
	    err = controlErr;	/* not supported */
	    break;
	default:
	    err = controlErr;
	    break;
	}
    }
#if defined(SERIALDEBUG)
    warning_trace_info ("serial control returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}

/*
 * NOTE:  SERSTATUS lies about everything except rdPend.
 */

A2(PUBLIC, OSErr, ROMlib_serialstatus, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    hiddenh h;
    LONGINT n;

    if (!(dcp->dCtlFlags & CWC(OPENBIT)))
	err = notOpenErr;
    else {
#if defined(SERIALDEBUG)
    warning_trace_info ("serial status csCode = %d", (LONGINT) CW(pbp->cntrlParam.csCode));
#endif
	h = (hiddenh) MR(dcp->dCtlStorage);
	switch (CW(pbp->cntrlParam.csCode)) {
	case SERGETBUF:
#if defined (LINUX) || defined (NEXTSTEP)
	    if (ioctl(HxX(h, fd), FIONREAD, &n) < 0)
#else
	    if (serial_bios_fionread(HxX(h, fd), &n) < 0)
#endif
		err = ROMlib_maperrno();
	    else {
#if defined(SERIALDEBUG)
    warning_trace_info ("serial status getbuf = %d", (LONGINT) n);
#endif
		*(LONGINT *) pbp->cntrlParam.csParam = CL(n);
		err = noErr;
	    }
	    break;
	case SERSTATUS:
#if defined (LINUX) || defined (NEXTSTEP)
	    if (ioctl(HxX(h, fd), FIONREAD, &n) < 0)
#else
	    if (serial_bios_fionread (HxX(h, fd), &n) < 0)
#endif
		err = ROMlib_maperrno();
	    else {
		((SerStaRec *)pbp->cntrlParam.csParam)->cumErrs  = 0;
		((SerStaRec *)pbp->cntrlParam.csParam)->xOffSent = 0;
		((SerStaRec *)pbp->cntrlParam.csParam)->rdPend   = n>0 ? 1 : 0;
		((SerStaRec *)pbp->cntrlParam.csParam)->wrPend   = 0;
		((SerStaRec *)pbp->cntrlParam.csParam)->ctsHold  = 0;
		((SerStaRec *)pbp->cntrlParam.csParam)->xOffHold = 0;
		err = noErr;
	    }
	    break;
	default:
	    err = statusErr;
	    break;
	}
    }
#if defined(SERIALDEBUG)
    warning_trace_info ("serial status returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}

PRIVATE void restorecloseanddispose(hiddenh h)
{
#if defined (LINUX) || defined (NEXTSTEP)
#if defined(TERMIO)
    ioctl(HxX(h, fd), TCSETAW, &HxX(h, state));
#else
    ioctl(HxX(h, fd), TIOCSETP, &HxX(h, sgttyb));
    ioctl(HxX(h, fd), TIOCSETC, &HxX(h, tchars));
    ioctl(HxX(h, fd), TIOCLSET, &HxX(h, lclmode));
#endif
    close(HxX(h, fd));
    Uunlink(lockname);
#endif
    DisposHandle((Handle) h);
}

A2(PUBLIC, OSErr, ROMlib_serialclose, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    hiddenh h;

    if (dcp->dCtlFlags & CWC(OPENBIT)) {
	h = (hiddenh) MR(dcp->dCtlStorage);
	restorecloseanddispose(h);
	dcp->dCtlFlags &= CWC(~OPENBIT);
	err = noErr;
    } else
	err = notOpenErr;
#if defined(SERIALDEBUG)
    warning_trace_info ("serial close returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}
