/* Support routines for TNC2 emulator - PC version */

#include "global.h"
#include "asy.h"
#include "ax25.h"
#include "timer.h"
#include "slip.h"
#include "tnc2.h"

#define	 PIPEBUF	512			/* size of a pipe buffer */
#define	 FLOW_LOW	64			/* low-water mark */
#define	 FLOW_HIGH	480			/* high-water mark */

/* INT14 vector stored at a second location for use under DoubleDOS */
/* This location is at the end of the "intra-application comm area" */
static void (*far *int14vec)() = (void (*far *)()) 0x000004fcL;

static char tnc2installed = 0;			/* vector already set? */
extern void (*far tnc2ovec)();			/* location to store old int14 vec */
extern char nospace[];

/* routine that simulates a character device to the client */
/* this one is called from an INT14 replacement, in the context */
/* of the client!  so don't call to many other routines, and don't */
/* try to create pointers to variables in the stack!! (SS != DS) */
/* the parameters are the registers pushed by the assembly language */
/* routine, and it will pop them back on return. if returnval=0, it will */
/* jump to the original handler (to handle other ports) */
/* entry conditions: AH=function code  AL=char or param	 DX=port (from 0) */
/* exit conditions: AX=return value  HDL=handler code */
int
int14(di,dx,cx,bx,ax,ds,es)
unsigned short di,dx,cx,bx,ax,ds,es;
{
    register struct tnc *tnc2;
    int i;

    for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next)
	if (tnc2->dev == dx + 1)		/* this one handled by us? */
	     break;

    if (tnc2 == NULLTNC)			/* when not found... */
	return 0;				/* let someone else deal with it */

    switch (ax >> 8)				/* switch on function code */
    {
    case 1:					/* Send character */
	i = 250;

	while (((!(tnc2->status & TS_TXBUF) &&	/* not allowed to buffer? */
		 tnc2->input.num != 0) ||	/* already something there */
		tnc2->input.num == PIPEBUF) &&	/* buffer full? */
	       --i)				/* not the max tries? */
	    giveup();				/* give NET a chance to read */

	if (tnc2->input.num == PIPEBUF) {	/* (still) full? */
	    tnc2->status |= TS_RXOVR;		/* then we have an overrun */
	    ax = 0x8000 | (ax & 0xff);		/* 'timeout' error + char */
	    goto rxstatus;			/* plus the receiver status */
	}

	*tnc2->input.in++ = uchar(ax);		/* save the character */
	if (tnc2->input.in == tnc2->input.end)
	    tnc2->input.in = tnc2->input.begin;
	if (++(tnc2->input.num) >= FLOW_HIGH)
	    tnc2->status |= TS_RXSTOP;

	/* finally, return the status in AH and sent char in AL */

	ax &= 0xff;				/* keep only char */
	goto trxstatus;

    case 2:					/* Receive character */
	i = 50;

	while (tnc2->output.num == 0 && --i)	/* no char(s) in buffer? */
	    giveup();

	if (tnc2->output.num == 0){		/* still nothing? */
	    ax = 0x8048 | (tnc2->status & TS_DCD); /* 'timeout' error */
	    goto trxstatus;
	}

	ax = uchar(*tnc2->output.out++);	/* AL is received char */
	if (tnc2->output.out == tnc2->output.end)
	    tnc2->output.out = tnc2->output.begin;
	if (--(tnc2->output.num) < FLOW_LOW)
	    tnc2->status &= ~TS_TXSTOP;

	goto trxstatus;				/* add tx & rx status in AH */

    case 0:					/* Initialize */
	/* flush the input and output buffers */
	tnc2->input.in = tnc2->input.out = tnc2->input.begin;
	tnc2->input.num = 0;
	tnc2->output.in = tnc2->output.out = tnc2->output.begin;
	tnc2->output.num = 0;

	/* keep only the DCD and TXBUF bits in the status (rest is volatile) */
	tnc2->status &= TS_DCD | TS_TXBUF;
						/* return status like: */
    case 3:					/* Status request */
allstatus:
	if (tnc2->input.num != 0 &&		/* something in buffer? */
	    !(tnc2->status & TS_TXBUF))		/* not allowed to buffer? */
	    giveup();				/* let NET read the char now */

	ax = 0x0048 | (tnc2->status & TS_DCD);	/* default status = DSR DDCD */

	if (tnc2->input.num < FLOW_LOW ||	/* below flow control treshold? */
	    (!(tnc2->status & TS_RXSTOP) && tnc2->input.num < FLOW_HIGH))
	    ax |= 0x10;				/* turn on CTS */

trxstatus:
	if (tnc2->input.num == 0)		/* buffer empty? */
	    ax |= 0x6000;			/* then turn on TEMT and THRE */

	if ((tnc2->status & TS_TXBUF) &&	/* allowed to buffer? */
	    tnc2->input.num < PIPEBUF)		/* buffer not yet full? */
	    ax |= 0x2000;			/* then turn on THRE */

rxstatus:
	if (tnc2->output.num != 0)		/* char(s) in buffer? */
	    ax |= 0x0100;			/* turn on data ready */

	if (tnc2->status & TS_TXOVR) {		/* transmitter overrun? */
	    tnc2->status &= ~TS_TXOVR;
	    ax |= 0x0200;			/* turn on overrun error */
	}

	break;

    case 4:					/* Inquiry */
	ax = 0xaa55;				/* Return AA55 to caller */
	break;

    case 5:					/* Drop DTR & RTS (MBBIOS) */
	tnc2->status |= TS_TXHOLD;
	goto allstatus;

    case 6:					/* Raise DTR & RTS (MBBIOS) */
	tnc2->status &= ~TS_TXHOLD;
	goto allstatus;

    case 7:					/* Send Break (MBBIOS) */
	i = 150;				/* First allow buffer to drain */

	while (tnc2->output.num != 0 && --i)	/* char(s) in buffer? */
	    giveup();

	tnc2->status |= TS_BREAK;		/* set the "BREAK Received" flag */
	goto allstatus;

    case 8:					/* Non-destr Read (MBBIOS) */
	ax = uchar(*tnc2->output.out);		/* AL is next char */
	goto trxstatus;				/* add tx & rx status in AH */

    case 9:					/* Set/Get Options (MBBIOS 3.2) */
	if (ax & TS_TXBUF) {			/* Only support TX Buf switching */
	    ax = 0x44 | (tnc2->status & TS_TXBUF); /* Highspeed/HW Handshake/TX Buffering */
	    tnc2->status |= TS_TXBUF;
	} else {
	    ax = 0x44 | (tnc2->status & TS_TXBUF); /* Highspeed/HW Handshake/TX Buffering */
	    tnc2->status &= ~TS_TXBUF;
	}
	break;

    case 10:					/* Write Buffer (MBBIOS 3.2) */
	while (cx != 0 &&			/* more to go? */
	       tnc2->input.num != PIPEBUF) {	/* buffer not full? */
	    *tnc2->input.in++ = ((char far *) ((long) es << 16))[di++];
	    if (tnc2->input.in == tnc2->input.end)
		tnc2->input.in = tnc2->input.begin;
	    if (++(tnc2->input.num) >= FLOW_HIGH)
		tnc2->status |= TS_RXSTOP;
	    cx--;
	}

	goto allstatus;

    case 11:					/* Read Buffer (MBBIOS 3.2) */
	i = cx;					/* move bytecount to temp */
	cx = 0;					/* number of bytes done */

	while (i-- && tnc2->output.num != 0) {	/* more requested & more available */
	    ((char far *) ((long) es << 16))[di++] = *tnc2->output.out++;
	    if (tnc2->output.out == tnc2->output.end)
		tnc2->output.out = tnc2->output.begin;
	    if (--(tnc2->output.num) < FLOW_LOW)
		tnc2->status &= ~TS_TXSTOP;
	    cx++;
	}

	goto allstatus;

    default:					/* Unrecognized request */
	ax = 0;					/* Let's just return 0 */
	break;
    }

    return 1;					/* we've handled it! */
}

/* public routines for use by tnc2 emulator */
/* initialize TNC2 client port */
int
tnc2_init (tnc2)
register struct tnc *tnc2;

{
    extern void tnc2vec();
#ifndef __TURBOC__
    void (*getvect())();
#endif
#ifdef COMBIOS
    unsigned int dev = tnc2->dev - 1;		/* BIOS-compat dev */
#endif

    tnc2->status = 0;				/* Unbuffered, all OFF */

#ifdef COMBIOS
    /* check if it is a BIOS-supported port, and assume external port if so */

    if (dev < ASY_MAX && combios(0x00e3,dev) != 0x00e3){
	if (com_init(dev) || com_speed(dev,9600,"h"))
	    return 1;

	return 0;
    }
#endif

    if ((tnc2->input.begin = malloc(PIPEBUF)) == NULLCHAR){
	printf(nospace);
	return 1;
    }
    tnc2->input.in = tnc2->input.out = tnc2->input.begin;
    tnc2->input.end = tnc2->input.begin + PIPEBUF;
    tnc2->input.num = 0;

    if ((tnc2->output.begin = malloc(PIPEBUF)) == NULLCHAR){
	free(tnc2->input.begin);
	printf(nospace);
	return 1;
    }
    tnc2->output.in = tnc2->output.out = tnc2->output.begin;
    tnc2->output.end = tnc2->output.begin + PIPEBUF;
    tnc2->output.num = 0;

    if (!tnc2installed) {			/* not yet installed? */
	tnc2ovec = getvect(0x14);		/* save existing vector */
	setvect(0x14,tnc2vec);			/* install our handler */
	*int14vec = tnc2vec;			/* save a second time */
	tnc2installed = 1;
    }

    return 0;
}

/* terminate TNC2 client port */
void
tnc2_term (tnc2)
register struct tnc *tnc2;

{
    if (tnc2s == NULLTNC && tnc2installed) {	/* is this the last one? */
	setvect(0x14,tnc2ovec);			/* restore the old handler */
	*int14vec = NULLVFP;			/* also reset saved vec */
	tnc2installed = 0;
    }

#ifdef COMBIOS
    if (tnc2->input.begin != NULLCHAR)		/* BIOS port has no buffers! */
#endif
    {
	free(tnc2->input.begin);
	free(tnc2->output.begin);
    }
}

/* read character from client, return -1 if none ready	*/
int
tnc2_ichar (tnc2)
register struct tnc *tnc2;

{
    unsigned char c;

#ifdef COMBIOS
    if (tnc2->input.begin == NULLCHAR) {	/* test for BIOS port */
	if (!(combios(0x0300,tnc2->dev - 1) & 0x0100)) /* test if data ready */
	    return TCH_NOCHAR;

	return uchar(combios(0x0200,tnc2->dev - 1)); /* get character and return */
    }
#endif

    if (tnc2->status & TS_BREAK) {		/* received a BREAK */
	tnc2->status &= ~TS_BREAK;
	return TCH_BREAK;
    }

    if (tnc2->input.num == 0)			/* nothing in buffer? */
	return TCH_NOCHAR;

    c = *tnc2->input.out++;
    if (tnc2->input.out == tnc2->input.end)
	tnc2->input.out = tnc2->input.begin;
    if (--(tnc2->input.num) < FLOW_LOW)
	tnc2->status &= ~TS_RXSTOP;

    return c;
}

/* send character to client, return 0 when blocked */
/* when character is <0, only check if it is blocked */
int
tnc2_ochar (tnc2,c)
register struct tnc *tnc2;
int c;

{
#ifdef COMBIOS
    if (tnc2->output.begin == NULLCHAR) {	/* test for BIOS port */
	if (c < 0) {				/* status check? */
	    if (combios(0x0300,tnc2->dev - 1) & 0x2000) /* test if buffer space */
		return 1;

	    return 0;
	}

	if (combios(0x0100 | c,tnc2->dev - 1) & 0x8000) /* send character */
	    return 0;				/* got a timeout! (unlikely) */

	return 1;
    }
#endif

    if (c < 0) {			/* a status check */
	if (tnc2->status & TS_TXHOLD)	/* held by "drop RTS" */
	    return 0;

	if (tnc2->output.num < FLOW_LOW)
	    return 1;

	/* maybe we better be conservative and don't allow him to go...
	if (!(tnc2->status & TS_TXSTOP) && tnc2->output.num < FLOW_HIGH)
	    return 1;	(he will try again later!) */

	return 0;
    }

    if (tnc2->output.num == PIPEBUF) {	/* buffer full? */
	giveup();			/* give a chance to read chars */
	if (tnc2->output.num == PIPEBUF) { /* still no space? */
	    tnc2->status |= TS_TXOVR;	/* transmitter overrun */
	    return 0;
	}
    }

    *tnc2->output.in++ = c;
    if (tnc2->output.in == tnc2->output.end)
	tnc2->output.in = tnc2->output.begin;
    if (++(tnc2->output.num) >= FLOW_HIGH)
	tnc2->status |= TS_TXSTOP;

    return 1;
}

/* set flowcontrol method for specified client */
/* method 0=none 1=soft (XON/XOFF) 2=hard (RTS/CTS) */
void
tnc2_flow (tnc2,method)
register struct tnc *tnc2;
int method;

{
#ifdef COMBIOS
    if (tnc2->output.begin == NULLCHAR)		/* BIOS port? */
	com_speed(tnc2->dev - 1,9600,"nxh"[method]);
#endif
}
