/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1986 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the authors.                                                    */
/* | o  | ||    Dave Baker     Ed Burnette  Stan Chow    Jay Denebeim        */
/* |  . |//     Gordon Keener  Jack Rouse   John Toebes  Doug Walker         */
/* ======          BBS:(919)-471-6436      VOICE:(919)-469-4210              */
/*                                                                           */
/* Contributed to Columbia University for inclusion in C-Kermit.             */
/*
  Permission is granted to any individual or institution to use this
  software as long as it is not sold for profit.  This copyright notice must be
  retained.  This software may not be included in commercial products without
  written permission of Columbia University.
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

char *ckxv = "Amiga tty I/O 1.6 92/01/15"
/*
  "$Id: ckitio.c,v 1.6 92/01/15 17:12:35 swalton Exp Locker: swalton $";
*/
/*  C K I T I O  --  Serial and Console I/O support for the Amiga */

/*
 Author: Jack Rouse, The Software Distillery
 Based on the CKUTIO.C module for Unix

 Modified for Manx Aztec C and Version 1.2 and forward of Amiga's OS by
 Stephen Walton of California State University, Northridge,
 ecphssrw@afws.csun.edu.  Further mods documented in ckiker.upd.

 $Log:	ckitio.c,v $
oRevision 1.6  92/01/15  17:12:35  swalton
oAdded Long BREAK support with new ttsndlb() routine.
o
oAdded support for multiple devices;  the SET LINE command now takes a
oline of the form "device/unit".
o
 *  Revision 1.5  91/07/18  16:04:57  swalton
 *  ttinl() now null terminates a received packet correctly.
 *
 *  Revision 1.4  91/05/29  09:08:57  swalton
 *  1.  Changed function definitions to prototype style.  Required adding
 *      a few forward declarations.
 *  2.  Removed includes of stdio.h, stdlib.h, and string.h, as they are
 *      now pulled in by ckcdeb.h, provided we compile with -DCK_ANSILIBS.
 *
 *  Revision 1.3  90/11/19  21:46:54  swalton
 *  Modifications for compiling with SAS/C Version 5.10, courtesy of
 *  Larry Rosenman (ler@erami.lonestar.org, ler on BIX)
 *
 *  Revision 1.2  90/11/07  14:42:07  swalton
 *  Version 1.2--released to world as first beta test version simultaneously
 *  with release of edit 5A(160).
 *
 *  Revision 1.1  90/07/12  22:30:11  swalton
 *  Rather extensive changes were made to ckitio.c, mainly to add new functions
 * required for the proper operation of C Kermit 5A(149).  They are not listed
 * in detail here;  refer to the parts of the C Kermit interface document
 * (file ckasys.doc in the Kermit archive) for the portions labeled *NEW*.
 * These will point you at the code revisions.
 *
 * Revision 1.0  90/04/30  11:54:27  swalton
 * Initial revision
 *
 */

#include "ckcdeb.h"
#include "ckcker.h"
#include "ckcnet.h"
#include "exec/types.h"
#include "exec/exec.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#define fh_Interact fh_Port
#define fh_Process fh_Type
#include "intuition/intuition.h"
#include "intuition/intuitionbase.h"
#define BREAKSIGS (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)
#ifdef AZTEC_C
#include "fcntl.h"
#include "signal.h"
#include <functions.h>
char *ckxsys = " Commodore Amiga (Aztec_C)";	/* system name */
#else
#ifdef __SASC
#include <fcntl.h>
#include <signal.h>
#include <ios1.h>		/* defines ufbs structure */
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
char *ckxsys = " Commodore Amiga (SAS/C)";	/* system name */
#endif
#endif

/* external definitions */
char *dftty = SERIALNAME;		/* serial device name */
int dfloc = 1;				/* serial line is external */
int dfprty = 0;				/* default parity is none */
int ttprty = 0;				/* parity in use */
int dfflow = 1;				/* default flow control is on */
int backgrd = 0;			/* default to foreground */
int ckxech = 0;				/* echo in case redirected stdin */
int tvtflg = 0;				/* Flag for ttvt() called. */
int ttcarr = 0;				/* Carrier detection mode */
int ttnproto = NP_NONE;			/* Protocol for network device */

struct Process *CurProc;		/* current process */
struct CommandLineInterface *CurCLI;	/* current CLI info */
struct IntuitionBase *IntuitionBase;	/* ptr to Intuition lib */

/* static definitions */
static struct MsgPort *serport;		/* message port for serial comm */
static struct MsgPort *conport;		/* console packet port */
static struct timerequest *TimerIOB;	/* timer request */
static struct IOExtSer *ReadIOB;	/* serial input request */
static struct IOExtSer *WriteIOB;	/* serial output request */
static struct DosPacket *conpkt;	/* console I/O packet */
static WORD serialopen;			/* true iff serial device open */
static WORD timeropen;			/* true iff timer device open */
static WORD pendwrite;			/* true iff WriteIOB in use */
static WORD pendread;			/* true iff ReadIOB in use */
static WORD pendconsole;		/* true when console read pending */
static int queuedser;			/* serial pushback char or -1 */
static UBYTE serbufc;			/* char buffer for read ahead I/O */
#define NTTOQ 64			/* connect output queue size */
static char ttoq[NTTOQ];		/* connect output queue */
static int nttoq;			/* number of chars in ttoq */
static int pttoq;			/* next char to output in ttoq */
static int queuedcon;			/* contti pushback char or -1 */
static LONG intsigs;			/* signals for aborting serial I/O */
static int (*inthdlr)(int);		/* function to signal break to */
static BPTR rawcon;			/* file handle for RAW: window */
static BPTR saverr;                     /* saved stderr file handle */
static APTR savewindow;			/* saved process WindowPtr */
static APTR pushwindow;			/* pushed process WindowPtr */
static struct DateStamp prevtime;	/* saved time value */


/* AmigaDOS support (from ckiutl.c) */
struct DosPacket *CreatePacket();
VOID DeletePacket();

#ifdef AZTEC_C
/* translate Unix file handle (0, 1, or 2) to AmigaDOS file handle */
#define DOSFH(n) (_devtab[n].fd)
/* translate Unix file handle (0, 1, or 2) to Aztec file handle */
#define FILENO(n) (n)
extern int Enable_Abort;
#else
/* Lattice runtime externals */
#ifdef __SASC
#define DOSFH(n) (chkufb(n)->ufbfh)
#define FILENO(n) (n)
#endif
#endif

/*
 * Under ANSI C, pointer-pointer assignments are illegal without an
 * explicit cast.  So, we define the following to make such casts short.
 */
#define IOR struct IORequest

/*
 * Forward declarations
 */
void reqres(void);
static void testint(long);

/*
 * make note of a serial error and quit
 */
static
Fail(char *msg)
{
	syscleanup();
	fprintf(stderr, msg);
	fprintf(stderr, "\n");
	exit(2);
}
/*
 * default interrupt handler
 */
static int
defhdlr(int foo)
{
	printf("*** BREAK ***\n");
	doexit(BAD_EXIT, 10);
}

/*
 *  sysinit -- Amiga specific initialization
 */
int
sysinit(void)
{
	struct IOExtSer *iob;

	/* set current process info */
	CurProc = (struct Process *)FindTask((char *)NULL);
	CurCLI = (struct CommandLineInterface *)BADDR(CurProc->pr_CLI);
	backgrd = (CurCLI == NULL || CurCLI->cli_Background);
	savewindow = CurProc->pr_WindowPtr;

	/* default interrupts to exit handler */
	intsigs = BREAKSIGS;
	inthdlr = defhdlr;
#ifdef __SASC
	signal(SIGINT, SIG_IGN);
#else
	Enable_Abort = 0;
#endif

	/* allocate console ports and IO blocks */
	if ((conport = CreatePort((char *)NULL, 0L)) == NULL)
		Fail("no console MsgPort");
	if ((conpkt = CreatePacket()) == NULL)
		Fail("no console packet");

	/* allocate serial ports and IO blocks */
	if ((serport = CreatePort((char *)NULL, 0L)) == NULL)
		Fail("no serial MsgPort");
	iob = (struct IOExtSer *)CreateExtIO(serport,(LONG)sizeof(*iob));
	if ((WriteIOB = iob) == NULL) Fail("no WriteIOB");
	iob = (struct IOExtSer *)CreateExtIO(serport,(LONG)sizeof(*iob));
	if ((ReadIOB = iob) == NULL) Fail("no ReadIOB");

	/* open the timer device */
	TimerIOB = (struct timerequest *)
		   CreateExtIO(serport,(LONG)sizeof(*TimerIOB));
	if (TimerIOB == NULL) Fail("no TimerIOB");
	if (OpenDevice(TIMERNAME, (LONG)UNIT_VBLANK, (IOR *)TimerIOB, 0L) != 0)
		Fail("no timer device");
	timeropen = TRUE;

	/* open the Intuition library */
	if (!IntuitionBase &&
	    (IntuitionBase = (struct IntuitionBase *)
			     OpenLibrary("intuition.library", 0L) ) == NULL )
		Fail("can't open Intuition");

	/* open the serial device to get configuration */
	iob->io_SerFlags = SERF_SHARED;
	if (OpenDevice(SERIALNAME, 0L, (IOR *)iob, 0L) != 0)
		Fail("can't open serial.device");
	/* set parameters from system defaults */
	dfflow   = !(iob->io_SerFlags & SERF_XDISABLED);
	/*
	 * Set default (startup) parity from Preferences settings.
 	 */
	if (iob->io_SerFlags & SERF_PARTY_ON) 	/* Parity is on */
		if (iob->io_ExtFlags & SEXTF_MSPON)	/* Space or mark */
			if (iob->io_ExtFlags & SEXTF_MARK)
				dfprty = 'm';		/* Mark parity */
			else
				dfprty = 's';		/* Space parity */
		else					/* Even or odd */
			if (iob->io_SerFlags & SERF_PARTY_ODD)
				dfprty = 'o';		/* Odd parity */
			else
				dfprty = 'e';		/* Even parity */
	else
		dfprty = 0;				/* No parity. */
	ttprty = dfprty;

	CloseDevice((IOR *)iob);
	serialopen = FALSE;
	return(0);
}

/*
 * syscleanup -- Amiga specific cleanup
 */
syscleanup(void)
{
	/* close everything */
	if (serialopen) CloseDevice((IOR *)ReadIOB);
	if (timeropen) CloseDevice((IOR *)TimerIOB);
	if (TimerIOB) DeleteExtIO((IOR *)TimerIOB);
	if (WriteIOB) DeleteExtIO((IOR *)WriteIOB);
	if (ReadIOB) DeleteExtIO((IOR *)ReadIOB);
	if (serport) DeletePort(serport);
	if (conpkt) DeletePacket(conpkt);
	if (conport) DeletePort(conport);
	reqres();
	if (IntuitionBase)
	{
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}

	/* reset standard I/O */
	if (rawcon > 0)
	{
		/* restore Lattice AmigaDOS file handles */
		DOSFH(0) = Input();
		DOSFH(1) = Output();
		DOSFH(2) = saverr;
		Close(rawcon);
	}
	return 1;
}

/*
 * reqoff -- turn requestors off
 *    When AmigaDOS encounters an error that user intervention can fix
 *    (like inserting the correct disk), it normally puts up a requestor.
 *    The following code disables requestors, causing an error to be
 *    returned instead.
 */
void
reqoff(void)
{
	pushwindow = CurProc->pr_WindowPtr;
	CurProc->pr_WindowPtr = (APTR)-1;
}
/*
 * reqpop -- restore requesters to action at last reqoff
 */
void
reqpop(void)
{
	CurProc->pr_WindowPtr = pushwindow;
}

/*
 * reqres -- restore requestors to startup action
 */
void
reqres(void)
{
	CurProc->pr_WindowPtr = savewindow;
}

/*
 * KillIO -- terminate an I/O request
 */
static int
KillIO(struct IORequest *iob)
{
	AbortIO(iob);
	return((int)WaitIO(iob));
}
/*
 * DoIOQuick -- DoIO with quick IO
 * This should not be used where waiting is expected since
 *    it cannot be interrupted.
 */
static int
DoIOQuick(struct IORequest *iob)
{
	/* do I/O with quick option, wait around if necessary */
	iob->io_Flags = IOF_QUICK;
	BeginIO(iob);
	if (!(iob->io_Flags & IOF_QUICK))
		WaitIO(iob);

	/* return the error, if any */
	return((int)iob->io_Error);
}

/*
 * ttopen -- open the serial device
 *    If already open, returns 0 immediately.
 *    Otherwise, the ttname is compare to SERIALNAME and used to
 *    open the serial device, and, if the value of *lcl is < 0, it is
 *    reset to 1 indicating local mode.  Returns -1 on error.
 *    timo is the length of time to wait before flunking open;  we don't
 *    need this feature on the Amiga.
 */
int
ttopen(char * ttname, int *lcl, int modem, int timo)
{
	struct IOExtSer *iob = ReadIOB;
	char *p;
	ULONG unit;

	if (serialopen) return(0);	/* ignore if already open */
	if (modem < 0) return -1;	/* We don't do networks yet. */

	/* verify the serial name */
#if 0
	if (strcmp(ttname, SERIALNAME) != 0) return(-1);
#endif

	/* set open modes.  We no longer open in shared mode. */
	iob->io_SerFlags = (modem > 0 ? SERF_7WIRE : 0);

	/* parse device name as device/unit */
	if ((p = strchr(ttname, '/')) == NULL)
	    unit = 0;
	else {
	    unit = (ULONG) atoi(p + 1);
	    *p = '\0';
	}
	/* open the serial device */
	if (OpenDevice(ttname, unit, (IOR *)iob, 0L) != 0)
		return(-1);
	serialopen = TRUE;
	tvtflg = 0;
	pendread = pendwrite = pendconsole = FALSE;
	queuedser = -1;

	/* fill in the fields of the other IO blocks */
	*WriteIOB = *iob;

	/* set local mode */
	if (*lcl == -1)	*lcl = 1; /* always local */
	if (p) *p = '/';		/* restore slash */
	return(0);
}

/*
 * StartTimer -- start a timeout
 */
static VOID
StartTimer(LONG secs, LONG micro)
{
	TimerIOB->tr_node.io_Command = TR_ADDREQUEST;
	TimerIOB->tr_time.tv_secs  = secs;
	TimerIOB->tr_time.tv_micro = micro;
	SendIO((IOR *)TimerIOB);
}

/*
 * SerialWait -- wait for serial I/O to terminate
 *    return I/O error
 */
static int
SerialWait(struct IOExtSer *iob, int timeout)
{
	LONG sigs;
	struct timerequest *timer = TimerIOB;
	LONG waitsigs;

	/* set up timeout if necessary */
	if (timeout > 0) StartTimer((LONG)timeout, 0L);

	/* wait for completion, timeout, or interrupt */
	sigs = 0;
	waitsigs = (1L << serport->mp_SigBit) | intsigs;
	for (;;)
	{
		if (sigs & intsigs)
		{	/* interrupted */
			if (timeout > 0) KillIO((IOR *)timer);
			KillIO((IOR *)iob);
			testint(sigs);
			return(-1);
		}
		if (CheckIO((IOR *)iob))
		{
			if (timeout > 0) KillIO((IOR *)timer);
			return((int)WaitIO((IOR *)iob));
		}
		if (timeout > 0 && CheckIO((IOR *)timer))
		{
			KillIO((IOR *)iob);
			WaitIO((IOR *)timer);
			/* restart if XOFF'ed */
			iob->IOSer.io_Command = CMD_START;
			DoIOQuick((IOR *)iob);
			return(-1);
		}
		sigs = Wait(waitsigs);
	}
}

/*
 * TerminateRead -- wait for queued read to finish
 */
static int
TerminateRead(void)
{
	if (!pendread) return(0);
	if (WaitIO((IOR *)ReadIOB) == 0) queuedser = serbufc;
	pendread = FALSE;
	return((int)ReadIOB->IOSer.io_Error);
}

/*
 * TerminateWrite -- ensure WriteIOB is ready for reuse
 */
static int
TerminateWrite(int timeout)
{
	testint(0L);
	if (!pendwrite) return(0);
	pendwrite = FALSE;
	return(SerialWait(WriteIOB, timeout));
}

/*
 * SerialReset -- terminate pending serial and console I/O
 */
static void
SerialReset(void)
{
	if (pendread)
	{
		AbortIO((IOR *)ReadIOB); /* should work even if read finished */
		TerminateRead();
	}

	if (pendconsole)
	{	/* this does not happen normally */
		WaitPort(conport);
		GetMsg(conport);
		pendconsole = FALSE;
	}

	if (pendwrite)
		TerminateWrite(1);
}

/*
 * ttres -- reset serial device
 */
ttres()
{
	if (!serialopen) return(-1);

	/* reset everything */
	SerialReset();
	ReadIOB->IOSer.io_Command = CMD_RESET;
	tvtflg = 0;
	return(DoIOQuick((IOR *)ReadIOB) ? -1 : 0);
}

/*
 * ttclos -- close the serial device
 */
int
ttclos(int foo)
{
	if (!serialopen) return(0);
	if (ttres() < 0) return(-1);
	CloseDevice((IOR *)ReadIOB);
	serialopen = FALSE;
	tvtflg = 0;
	return(0);
}

/*
 * tthang -- hang up phone line
 *    Drops DTR by closing serial.device
 */
int
tthang(void)
{	return((serialopen) ? ttclos(0) : -1); }

/*
 * ttpkt -- set serial device up for packet transmission
 *    sets serial parameters
 */
int
ttpkt(long speed, int flow, int parity)
{
	extern UBYTE eol;
	struct IOExtSer *iob = ReadIOB;

	if (!serialopen || pendread) return(-1);

	/* terminate any pending writes */
	TerminateWrite(1);

	/* fill in parameters */
	iob->io_CtlChar = 0x11130000;
	if (speed >= 0 && ttsspd((int) (speed / 10)) >= 0) iob->io_Baud = speed;
	/*
	 * Notice the dopar(eol) here to set the EOL character with the
	 * appropriate parity.  See also ttinl().
	 */
	setmem(&iob->io_TermArray, sizeof(struct IOTArray), dopar(eol));
	iob->io_ReadLen = iob->io_WriteLen = 8;
	iob->io_StopBits = 1;
	if (flow)
		iob->io_SerFlags &= ~SERF_XDISABLED;
	else
		iob->io_SerFlags |= SERF_XDISABLED;
	/* if no flow and high baud rate, RAD_BOOGIE is appropriate */
	if (!flow && iob->io_Baud > 19200)
		iob->io_SerFlags |= SERF_RAD_BOOGIE;
	else
		iob->io_SerFlags &= ~SERF_RAD_BOOGIE;

	/*
	 * Parity setting.  For packet send/receive, we turn off the
	 * Amiga's internal parity generation and checking, as this code
	 * does it itself (which makes it bigger and slower...).  We
	 * save the current parity for ttinl().
	 */

	ttprty = parity;
	iob->io_SerFlags &= ~(SERF_EOFMODE|SERF_PARTY_ON|SERF_PARTY_ODD);
	iob->io_ExtFlags = 0;		/* MUST BE ZERO unless Mark or Space. */

	/* set the parameters */
	iob->IOSer.io_Command = SDCMD_SETPARAMS;
	if (DoIOQuick((IOR *)iob) != 0) return(-1);
	tvtflg = 0;
	return(ttflui());
}

/*
 * ttvt -- set up serial device for connect mode.  This is almost the same
 * as ttpkt() on the Amiga, except we save the settings and a flag and return
 * without doing anything if we've already been called with the same
 * values.
 */
int
ttvt(long speed, int flow) {
	static long ospeed = -1;
	static int oflow = -9;

	if (tvtflg != 0 && ospeed == speed && oflow == flow)
		return 0;
	if (ttpkt(speed, flow, 0) < 0)
		return -1;
	ospeed = speed;			/* Save speed */
	oflow = flow;			/* and flow control set */
	tvtflg = 1;			/* and flag we've been called */
	return 0;
}

/*  T T S S P D  --  Checks and sets transmission rate.  */

/*  Call with speed in characters (not bits!) per second. */
/*  Returns 0 if successful, -1 otherwise. */

int
ttsspd(int cps) {
    long s;

#ifdef	NETCONN
    if (netconn) return (0);
#endif	/* NETCONN */

    if (cps < 0) return(-1);
    s = -1;

    /* First check that the given speed is valid. */

    switch (cps) {
      case 0:   s = 0;    break;
      case 5:   s = 50;   break;
      case 7:   s = 75;   break;
      case 11:  s = 110;  break;
      case 15:  s = 150;  break;
      case 20:  s = 200;  break;
      case 30:  s = 300;  break;
      case 60:  s = 600;  break;
      case 120: s = 1200; break;
      case 180: s = 1800; break;
      case 240: s = 2400; break;
      case 480: s = 4800; break;
      case 888: return -1;		/* 888 means 75/1200 split speed */
      case 960: s = 9600; break;
      case 1920: s = 19200; break;
      case 3840: s = 38400; break;
      default: s = -1; break;
    }
    return(s > 0 ? cps : -1);
}

/* T T G S P D  -  Get speed of currently selected tty line  */

/*
  Read speed from serial.device, or, if not open, return the value in
  the current ReadIOB.
*/
long
ttgspd(void) {				/* Get current tty speed */
	struct IOExtSer *myread = ReadIOB;

	if (!serialopen)
		if (myread != NULL) return((long)myread->io_Baud);
		else return -1;
	testint(0L);
	if (pendread && !CheckIO((IOR *)myread)) return(0);
	if (TerminateRead() != 0) return(-1);
	myread->IOSer.io_Command = SDCMD_QUERY;
	return((DoIOQuick((IOR *)myread) == 0)
			? (long)myread->io_Baud
			: -1);
}

/*
 * ttflui -- flush serial device input buffer
 */
int
ttflui(void)
{
	if (!serialopen || pendread) return(-1);
	queuedser = -1;
	ReadIOB->IOSer.io_Command = CMD_CLEAR;
	return(DoIOQuick((IOR *)ReadIOB) ? -1 : 0);
}

/*
 * ttfluo -- flush serial output buffer
 */
int
ttfluo(void)
{
	if (!serialopen || pendwrite) return -1;
	WriteIOB->IOSer.io_Command = CMD_CLEAR;
	return(DoIOQuick((IOR *)WriteIOB) ? -1 : 0);
}


static struct IntuiText BodyText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Interrupt Requested", NULL
};

static struct IntuiText ContinueText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Continue", NULL
};

static struct IntuiText AbortText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Exit C-Kermit", NULL
};

/*
 * test for and catch interrupt
 */
void
testint(LONG sigs)
{
	int (*catch)(int);

	/* test for and reset caught interrupt signals */
	if (((sigs | SetSignal(0L, (LONG)BREAKSIGS)) & intsigs) && inthdlr)
	{
		if (AutoRequest((struct Window *)NULL,
				&BodyText,
				&ContinueText,
				&AbortText,
				0L, 0L, 260L, 55L) )
			return;
		catch = inthdlr;
		inthdlr = NULL;
		intsigs = 0;
		(void) (*catch)(0);
	}
}

/*
 * conint -- set console interrupt handler and suspend handler.
 */
void
conint(int (*newhdlr)(int), int (*stophdlr)(int))
{
	testint(0L);			/* handle any pending interrupts */
	inthdlr = newhdlr;		/* set the new handler */
	intsigs = BREAKSIGS;		/* note signal caught */
}

/*
 * connoi -- disable interrupt trapping
 */
void
connoi(void)
{
	inthdlr = NULL;			/* disable interrupts */
	intsigs = 0;			/* note signal ignored */
	testint(0L);			/* ignore pending interrupts */
}

/*
 * ttchk -- return number of chars immediately available from serial device
 */
int
ttchk(void)
{
	struct IOExtSer *myread = ReadIOB;

	if (!serialopen) return(-1);
	testint(0L);
	if (pendread && !CheckIO((IOR *)myread)) return(0);
	if (TerminateRead() != 0) return(-1);
	myread->IOSer.io_Command = SDCMD_QUERY;
	return((DoIOQuick((IOR *)myread) == 0)
			? ((queuedser >= 0 ? 1 : 0) + (int)myread->IOSer.io_Actual)
			: -1);
}

/*
 * ttxin -- get n characters from serial device.  This routine should
 * only be called when we know that there are at least n characters
 * ready to be read.
 */
int
ttxin(int n, CHAR *buf)
{	return(ttinl(buf, n, 0, 0)); }

#ifdef PARSENSE

extern CHAR partab[];

/*  P A R C H K  --  Check if Kermit packet has parity  */

/*
  Call with s = pointer to packet, start = packet start character, n = length.
  Returns 0 if packet has no parity, -1 on error, or if packet has parity:
    'e' for even, 'o' for odd, 'm' for mark.  Space parity cannot be sensed.
*/
parchk(s,start,n) CHAR *s, start; int n; {
    CHAR s0, s1, s2, s3, sn;

    debug(F101,"parchk n","",n);
    debug(F101,"parchk start","",start);
    debug(F110,"parchk s",s,0);

    s0 = s[0] & 0x7f;			/* Mark field (usually Ctrl-A) */

    if (s0 != start || n < 5) return(-1); /* Not a valid packet */

/* Look at packet control fields, which never have 8th bit set */
/* First check for no parity, most common case. */

    if (((s[0] | s[1] | s[2] | s[3] | s[n-2]) & 0x80) == 0)
      return(0);			/* No parity */

/* Check for mark parity */

    if (((s[0] & s[1] & s[2] & s[3] & s[n-2]) & 0x80) == 0x80)
      return('m');			/* Mark parity */

/* Packet has some kind of parity */
/* Make 7-bit copies of control fields */

    s1 = s[1] & 0x7f;			/* LEN */
    s2 = s[2] & 0x7f;			/* SEQ */
    s3 = s[3] & 0x7f;			/* TYPE */
    sn = s[n-2] & 0x7f;			/* CHECK */

/* Check for even parity */

    if ((s[0] == partab[s0]) &&
        (s[1] == partab[s1]) &&
        (s[2] == partab[s2]) &&
	(s[3] == partab[s3]) &&
	(s[n-2] == partab[sn]))
      return('e');

/* Check for odd parity */

    if ((s[0] != partab[s0]) &&
        (s[1] != partab[s1]) &&
        (s[2] != partab[s2]) &&
	(s[3] != partab[s3]) &&
	(s[n-2] != partab[sn]))
      return('o');

/* Otherwise it's probably line noise.  Let checksum calculation catch it. */

    return(-1);
}
#endif /* PARSENSE */

/*
 * ttinc -- read character from serial line
 */
int
ttinc(int timeout)
{
	UBYTE ch;

	return((ttinl((CHAR *)&ch, 1, timeout, 0) > 0) ? (int)ch : -1);
}

/*
 * ttol -- write n chars to serial device.  For small writes, we have
 * a small local buffer which allows them to run asynchronously.  For
 * large writes, we do them synchronously.  This seems to be the best
 * compromise between speed and code simplicity and size.
 *
 * Stephen Walton, 23 October 1989
 */
int
ttol(CHAR *buf, int n)
{
	struct IOExtSer *mywrite = WriteIOB;
	static char outbuf[256];	/* safe place for output characters */

	if (!serialopen) return(-1);
	if (TerminateWrite(1) != 0) return(-1);
	pendwrite = TRUE;
	mywrite->IOSer.io_Command = CMD_WRITE;
	if (n <= 256) {
		movmem(buf, outbuf, n);
		mywrite->IOSer.io_Data    = (APTR)outbuf;
		mywrite->IOSer.io_Length  = n;
		SendIO((IOR *)mywrite);
	} else {
		mywrite->IOSer.io_Data	= (APTR) buf;
		mywrite->IOSer.io_Length	= n;
		DoIOQuick((IOR *)mywrite);
	}
	return(n);
}

/*
 * ttoc -- output single character to serial device
 */
int
ttoc(char c)
{	return(ttol((CHAR *) &c, 1)); }

/*
 * ttinl -- read from serial device, possibly with timeout and eol character
 *    reads up to n characters, returning the number of characters read
 *    if eol > 0, reading the eol character will terminate read
 *    if timeout > 0, terminates read if timeout elapses
 *    returns -1 on error, such as timeout or interrupt
 *
 *    Note that this is the single routine which does all character reading
 *    in Amiga C Kermit, and has some added "features" compared to, say,
 *    the Unix version.  If timeout is 0, this routine waits forever.
 *    If eol is zero, it is not used.
 *
 *    New for 5A(157) is the start parameter, which is the start-of-packet
 *    character.  Following the Unix example, we just read until eol,
 *    but return a bad packet if the first character we got doesn't agree
 *    with start.
 */
int
ttinl(CHAR *buf, int n, int timeout, CHAR eol)
{
        unsigned  mask;
	struct IOExtSer *myread = ReadIOB;
	int count;
	int nread, i;

	testint(0L);
 	if (!serialopen || pendread || n <= 0) return(-1);

	mask = (ttprty ? 0177 : 0377);	/* parity stripping mask */

	/* handle pushback */
	if (queuedser >= 0)
	{
		*buf = queuedser & mask;	/* Strip queued character. */
		queuedser = -1;
		if (*buf == eol || n == 1) return(1);
		++buf;
		--n;
		count = 1;
	}
	else
		count = 0;

	/* set up line terminator */
	if (eol > 0)
	{
		/*
		 * For reasons which are obscure to me, this batch of
		 * code generally fails.  Normally, this doesn't matter,
		 * because io_TermArray is set in ttpkt() above, and so
		 * this code is only executed if eol changes as a result
		 * of the initial packet negotiation.  I found the bug
		 * by inadvertently not using dopar(eol) in the setting
		 * of io_TermArray in ttpkt(), which did cause this code
		 * to be called if parity was MARK or EVEN (since in that
		 * case dopar(eol) != eol).
		 */

		if (dopar(eol) != *(UBYTE *)&myread->io_TermArray)
		{
			setmem(&myread->io_TermArray,
			       sizeof(struct IOTArray), dopar(eol));
			myread->IOSer.io_Command = SDCMD_SETPARAMS;
			if (DoIOQuick((IOR *)myread) != 0) {
				debug(F111, "SETPARAMS fails in ttinl()",
				      "io_Error", (int) myread->IOSer.io_Error);
				myread->io_TermArray.TermArray0 =
					myread->io_TermArray.TermArray1 = 0xffffffffu;
				return -1;
			}
		}
		myread->io_SerFlags |= SERF_EOFMODE;
	}
	else
		myread->io_SerFlags &= ~SERF_EOFMODE;

	/* set up the read */
	myread->IOSer.io_Command = CMD_READ;
	myread->IOSer.io_Data    = (APTR)buf;
	myread->IOSer.io_Length  = n;

	/* perform read quickly if possible */
	myread->IOSer.io_Flags = IOF_QUICK;
	BeginIO((IOR *)myread);
	if (myread->IOSer.io_Flags & IOF_QUICK)
		myread->IOSer.io_Flags = 0;
	else
		/* wait for read to complete if no QUICK. */
		if (SerialWait(myread, timeout) != 0)
			return -1;

	if (myread->IOSer.io_Error != 0)
		return -1;
#if COMMENT
	if (start != 0 && (buf[0] & mask) != start) /* Bad packet */
		return -1;
#endif
	/* Strip parity bits if need be. */
	nread = (int) myread->IOSer.io_Actual;
	if (ttprty)
		for (i = 0; i < nread; i++)
			buf[i] &= mask;
	if (nread > 1)
		buf[nread] = '\0';		/* Null terminate */
	return(count + nread);
}

/*
 * Sleeper -- perform an interruptible timeout
 */
static int
Sleeper(LONG secs, LONG micro)
{
	LONG sigs;
	LONG waitsigs;
	struct timerequest *timer = TimerIOB;

	if (!timeropen) return(-1);
	StartTimer(secs, micro);
	sigs = 0;
	waitsigs = (1L << serport->mp_SigBit) | intsigs;
	for (;;)
	{
		if (CheckIO((IOR *)timer))
		{
			WaitIO((IOR *)timer);
			return(0);
		}
		if (sigs & intsigs)
		{
			KillIO((IOR *)timer);
			testint(sigs);
			return(-1);
		}
		sigs = Wait(waitsigs);
	}
}

/*
 * sleep -- wait n seconds
 */
int
sleep(int n)
{	return(Sleeper((LONG)n, 0L)); }

/*
 * msleep -- wait n milliseconds
 */
int
msleep(int m)
{	return(Sleeper((LONG)(m / 1000), (m % 1000) * 1000L)); }


/*
 * rtimer -- reset elapsed time
 */
void
rtimer(void)
{	DateStamp(&prevtime); }

/*
 * gtimer -- get currently elapsed time in seconds
 */
int
gtimer(void)
{
	int x;
	struct DateStamp curtime;

	DateStamp(&curtime);
	x = ((curtime.ds_Days   - prevtime.ds_Days  ) * 1440 +
	     (curtime.ds_Minute - prevtime.ds_Minute) ) * 60 +
	     (curtime.ds_Tick   - prevtime.ds_Tick  ) / 50;
	return((x < 0) ? 0 : x );
}

/*
 * ztime -- format current date and time into string
 */
void
ztime(char **s)
{
   /*
    * The following date code taken from a USENET article by
    *    Tomas Rokicki(rokicki@Navajo.ARPA)
    */
   static char *months[] = { NULL,
      "January","February","March","April","May","June",
      "July","August","September","October","November","December"};
   static char buf[32];

   long n ;
   int m, d, y ;
   struct DateStamp datetime;

   DateStamp(&datetime);

   n = datetime.ds_Days - 2251 ;
   y = (4 * n + 3) / 1461 ;
   n -= 1461 * y / 4 ;
   y += 1984 ;
   m = (5 * n + 2) / 153 ;
   d = n - (153 * m + 2) / 5 + 1 ;
   m += 3 ;
   if (m > 12) {
      y++ ;
      m -= 12 ;
   }
   sprintf(buf, "%02d:%02d:%02d %s %d, %d",
           datetime.ds_Minute / 60, datetime.ds_Minute % 60,
	   datetime.ds_Tick / 50, months[m], d, y) ;
   *s = buf;
}

/*
 * congm -- save console modes
 */
int
congm(void)
{
	if (!saverr) saverr = DOSFH(2);
	return(0);
}

/*
 * CreateWindow -- create window and jam it into standard I/O
 */
int
CreateWindow(int esc)
{
	if (rawcon > 0) return(0);
	congm();

	if ((rawcon = Open("RAW:0/0/640/200/Kermit", (LONG)MODE_NEWFILE)) == 0)
		return(-1);
	DOSFH(0) = DOSFH(1) = DOSFH(2) = rawcon;

	/* if we create a window, don't abort on errors or echo */
	backgrd = FALSE;
	ckxech = 1;
	return(0);
}

/*
 * concb -- put console in single character wakeup mode
 */
int
concb(char esc)
{
	if (rawcon) return(0);
	if (CurCLI && CurProc->pr_CIS != CurCLI->cli_StandardInput)
		return(0);
	return(CreateWindow(esc));
}

/*
 * conbin -- put console in raw mode
 */
int
conbin(char esc)
{
	if (rawcon) return(0);
	if (CurCLI && CurProc->pr_CIS != CurCLI->cli_StandardInput)
		return(isatty(0) ? 0 : -1);
	return(CreateWindow(esc));
}

/*
 * conres -- restore console
 *    we actually restore in syscleanup()
 */
conres()
{	return(0); }

/*
 * conoc -- output character to console
 */
conoc(char c)
{
	putchar(c);
	fflush(stdout);
	testint(0L);
}

/*
 * conxo -- output x chars to console
 */
int
conxo(int n, char *buf)
{
	int retval;

	fflush(stdout);
	retval = write(FILENO(1), buf, n);
	testint(0L);
	return retval;
}

/*
 * conol -- output line to console
 */
int
conol(char *l)
{
	int retval;
	retval = fputs(l, stdout);
	fflush(stdout);
	testint(0L);
	return retval;
}

/*
 * conola -- output line array to console
 */
int
conola(char **l)
{
	for (; **l; ++l)
		if (conol(*l) < 0)
			return(-1);
	return 0;
}

/*
 * conoll -- output line with CRLF
 */
int
conoll(char *l)
{
	if (conol(l) < 0)
		return -1;
	if (conxo(2, "\r\n") < 0)
		return -1;
	return 0;
}

/*
 * conchk -- returns nonzero if characters available from console
 */
int
conchk(void)
{
	fflush(stdout);
	testint(0L);
	return(WaitForChar(DOSFH(0), 0L) != 0);
}

/*
 * coninc -- get input character from console
 */
int
coninc(int timeout)
{
	UBYTE ch;

	fflush(stdout);
	testint(0L);
	if (timeout > 0 && !WaitForChar(DOSFH(0), timeout * 1000000L))
		return(-1);
	if (read(FILENO(0), &ch, 1) < 1) return(-1);
	testint(0L);
	return((int)ch);
}

/*
 * T T S C A R R -- Copy desired character mode to global ttcarr for future
 * and later use.
 */
ttscarr(carrier) int carrier; {
    ttcarr = carrier;
    debug(F101, "ttscarr","",ttcarr);
    return(ttcarr);
}

static int
sendbreak(long time) {
	if (!serialopen) return(-1);
	/* flush queued output */
	TerminateWrite(1);
	nttoq = 0;
	pendwrite = TRUE;
	WriteIOB->IOSer.io_Command = SDCMD_SETPARAMS;
	WriteIOB->io_BrkTime = time;
	(void) DoIOQuick((IOR *)WriteIOB);
	pendwrite = TRUE;
	WriteIOB->IOSer.io_Command = SDCMD_BREAK;
	WriteIOB->io_SerFlags &= ~SERF_QUEUEDBRK;
	SendIO((IOR *)WriteIOB);
	return(0);
}

/*
 * ttsndb -- send a BREAK
 *    flushes queued and active output
 */
int
ttsndb(void)
{
	return(sendbreak(275000L));
}

/*
 * ttsndlb -- send a long BREAK (1.5 sec)
 */
int
ttsndlb(void) {
	return(sendbreak(1500000L));
}

/*  T T W M D M  --  Wait for modem signals  */

/*
  Wait up to timo seconds for all of the given modem signals to appear.
  mdmsig is a bit mask, in which:
   BM_CTS (bit 0) means wait for Clear To Send
   BM_DSR (bit 1) means wait for Data Set Ready
   BM_DCD (bit 2) means wait for Carrier Detect
  Returns:
  -3 Not implemented.
  -2 This line does not have modem control.
  -1 Timeout: time limit exceeded before all signals were detected.
   1 Success.
*/
int
ttwmdm(int mdmsig, int timo) {
    return(-3);
}

/*  T T G M D M  --  Get modem signals  */
/*
 Looks for the modem signals CTS, DSR, and CTS, and returns those that are
 on in as its return value, in a bit mask as described for ttwmdm.  Returns:
 -3 Not implemented
 -2 if the line does not have modem control
 -1 on error.
 >= 0 on success, with a bit mask containing the modem signals that are on.
*/

int
ttgmdm(void) {
    return(-3);
}


/*
 * ttocq -- write char to serial device, queueing if necessary
 *    returns -2 on overrun, -1 on serial error
 *    use only in connect mode
 */
int
ttocq(char c)
{
	int i;

	if (!serialopen) return(-1);
	if (pendwrite && CheckIO((IOR *)WriteIOB))
	{
		pendwrite = FALSE;
		if (WaitIO((IOR *)WriteIOB) != 0) return(-1);
	}
	if (pendwrite)
	{
		if (nttoq >= NTTOQ) return(-2);		/* overrun */
		ttoq[(pttoq + nttoq++) % NTTOQ] = c;
	}
	else if (nttoq == 0)
		return(ttoc(c));
	else
	{
		i = ttoc(ttoq[pttoq]);
		ttoq[(pttoq + nttoq) % NTTOQ] = c;
		pttoq = (pttoq + 1) % NTTOQ;
		if (i < 0) return(-1);
	}
	return(1);
}

/*
 * ttonq -- returns number of characters in serial output queue
 */
int
ttonq(void)
{	return(nttoq); }

/*
 * conttb -- prepare for contti() usage
 */
void
conttb(void)
{
	/* flush queued input and output */
	queuedcon = -1;
	pttoq = nttoq = 0;
}

/*
 * contte -- end contti() usage
 *    this can be called after a tthang, it which case ttres will already
 *    have done this cleanup
 */
void
contte(void)
{
	/* clear any pending ^C, ^D interrupts */
	testint(0L);

	/* terminate any pending I/O */
	if (serialopen) SerialReset();
}

/*
 * contti -- wait for console or tty input
 *    returns next console input or -1 when serial input available
 */
int
contti(void)
{
	int i;
	LONG waitsigs;
	struct DosPacket *pkt = conpkt;
	struct IOExtSer *myread = ReadIOB;
	static UBYTE conchar;
	BPTR dosfh = DOSFH(0);
	struct FileHandle *fh = (struct FileHandle *)BADDR(dosfh);

	if (queuedcon >= 0)
	{
		conchar = queuedcon;
		queuedcon = -1;
		return((int)conchar);
	}

	if (!pendconsole)
	{	/* start a console read */
		pkt->dp_Port = conport;
		pkt->dp_Type = ACTION_READ;
		pkt->dp_Arg1 = (LONG)dosfh;
		pkt->dp_Arg2 = (LONG)&conchar;
		pkt->dp_Arg3 = 1;
		PutMsg(fh->fh_Process, pkt->dp_Link);
		pendconsole = TRUE;
	}

	if (queuedser < 0 && !pendread)
	{	/* start a serial read */
		myread->IOSer.io_Command = CMD_READ;
		myread->IOSer.io_Data    = (APTR)&serbufc;
		myread->IOSer.io_Length  = 1;
		SendIO((IOR *)myread);
		pendread = TRUE;
	}

	waitsigs = (1L << serport->mp_SigBit) | (1L << conport->mp_SigBit);
	for (;;)
	{
		if (pendwrite && CheckIO((IOR *)WriteIOB))
		{
			pendwrite = FALSE;
			if (nttoq > 0)
			{
				i = ttoc(ttoq[pttoq]);
				pttoq = (pttoq + 1) % NTTOQ;
				--nttoq;
				if (i < 0) return(-1);
			}
		}

		/* give the console first chance */
		if (GetMsg(conport))
		{
			pendconsole = FALSE;
			if (pkt->dp_Res1 != 1) return(-1);
			/* translate CSI to ESC [ */
			if (conchar == 0x9B)
			{	conchar = 0x1B; queuedcon = '['; }
			return((int)conchar);
		}

		if (queuedser >= 0) return(-2);

		if (CheckIO((IOR *)myread))
			return((TerminateRead() == 0) ? -2 : -1);

		Wait(waitsigs);
	}
}

/* P S U S P E N D -- Put current process in background. */

/*
 * Even though this isn't supported on the Amiga, I return success anyway.
 * After all, the user can pop the window to the back and do something
 * else any time he wants.
 */

int
psuspend(int foo) {
    return 0;
}

/* P R I V _ functions -- all dummy on the Amiga. */

int
priv_ini(void) {
    return 0;
}


int
priv_on(void) {
    return 0;
}

int
priv_off(void) {
    return 0;
}

int
priv_can(void) {
    return 0;
}
