/* Support routines for TNC2 emulator - Atari ST version */

#ifdef MWC
#include <osbind.h>			/* os interface defines */
#endif
#include "global.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 */

#define	 SYSBASE	*((long *) 0x4f2L)	/* start of operating system */

static unsigned int biosvers;			/* set to BIOS version# */

#define	 BCONSTAT	((long (**)()) 0x51e)	/* Bconstat vector */
#define	 BCONIN		((long (**)()) 0x53e)	/* Bconin vector */
#define	 BCOSTAT	((long (**)()) 0x55e)	/* Bcostat vector */
#define	 BCONOUT	((void (**)()) 0x57e)	/* Bconout vector */

extern char nospace[];

/* routines that simulate a character device to the client */

static struct tnc *dev2tnc[8];			/* tnc struct per device */

static long
pconstat (dev)
    int dev;

{
    if (dev2tnc[dev]->output.num != 0)
	return -1;

    return 0;
}

static long
pconin (dev)
    int dev;

{
    register struct tnc *tnc2;
    char c;

    tnc2 = dev2tnc[dev];

    if (tnc2->output.num == 0)
	return TCH_NOCHAR;		/* that will fail, but cannot wait */

    c = *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;

    return uchar(c);
}

static long
pcostat (dev)
    int dev;

{
    register struct tnc *tnc2;

    tnc2 = dev2tnc[dev];

    if (tnc2->input.num != 0 &&			/* something buffered? */
	!(tnc2->status & TS_TXBUF))		/* not allowed to buffer? */
	return 0;				/* buffer full */

    if (tnc2->input.num < FLOW_LOW ||		/* below flow control treshold? */
	(!(tnc2->status & TS_RXSTOP) && tnc2->input.num < FLOW_HIGH))
	return -1;				/* buffer is not full */

    return 0;					/* buffer full */
}

static void
pconout (dev,c)
    int dev,c;

{
    register struct tnc *tnc2;

    tnc2 = dev2tnc[dev];

    if (tnc2->input.num == PIPEBUF)		/* buffer full? */
	tnc2->status |= TS_RXOVR;		/* then we have an overrun */
    else {
	*tnc2->input.in++ = c;
	if (tnc2->input.in == tnc2->input.end)
	    tnc2->input.in = tnc2->input.begin;
	if (++(tnc2->input.num) >= FLOW_HIGH)
	    tnc2->status |= TS_RXSTOP;
    }
}

/* public routines for use by tnc2 emulator */
/* initialize TNC2 client port */
int
tnc2_init (tnc2)
register struct tnc *tnc2;

{
    long sav_ssp;
    char *sysbase;

    tnc2->status = 0;				/* Unbuffered, all OFF */

    sav_ssp = Super(NULL);			/* access system data */
    sysbase = (char *) SYSBASE;			/* find OS */
    biosvers = *((unsigned int *) (sysbase + 2)); /* read BIOS version */
    Super(sav_ssp);

    switch (tnc2->dev)
    {
    case 0:
    case 1:
    case 3:
    case 6:
    case 7:
	if (biosvers < 0x0102){
	    printf("devices 0-7 need BIOS 1.2 (Blitter-TOS)\n");
	    return 1;
	}

	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;

	dev2tnc[tnc2->dev] = tnc2;
	sav_ssp = Super(NULL);			/* access system data */
	tnc2->save_vec[0] = BCONSTAT[tnc2->dev]; /* save old handlers */
	tnc2->save_vec[1] = BCONIN[tnc2->dev];
	tnc2->save_vec[2] = BCOSTAT[tnc2->dev];
	tnc2->save_vec[3] = BCONOUT[tnc2->dev];
	BCONSTAT[tnc2->dev] = pconstat;		/* set new handlers */
	BCONIN[tnc2->dev] = pconin;
	BCOSTAT[tnc2->dev] = pcostat;
	BCONOUT[tnc2->dev] = pconout;
	Super(sav_ssp);
	break;

    case 8:
	Rsconf(1,0,0x88,0x01,0x01,0);		/* 9600 baud, 8 bits, no parity */
	break;

    case 9:
	sav_ssp = Super(NULL);			/* access MIDI chip */
	*((char *) 0xfffffc04L) = 0x03;		/* RESET it */
	*((char *) 0xfffffc04L) = 0x96;		/* set 7812.5/9600 baud */
	Super(sav_ssp);
	break;

    default:
	printf("tnc2: unsupported device %d\n",tnc2->dev);
	return 1;
    }
    return 0;
}

/* terminate TNC2 client port */
void
tnc2_term (tnc2)
register struct tnc *tnc2;

{
    long sav_ssp;

    switch (tnc2->dev)
    {
    case 0:
    case 1:
    case 3:
    case 6:
    case 7:
	sav_ssp = Super(NULL);			/* access system data */
	BCONSTAT[tnc2->dev] = tnc2->save_vec[0]; /* restore old handlers */
	BCONIN[tnc2->dev] = tnc2->save_vec[1];
	BCOSTAT[tnc2->dev] = tnc2->save_vec[2];
	BCONOUT[tnc2->dev] = tnc2->save_vec[3];
	Super(sav_ssp);
	dev2tnc[tnc2->dev] = NULLTNC;
	free(tnc2->input.begin);
	free(tnc2->output.begin);
	break;
    }
}

/* read character from client, return -1 if none ready	*/
int
tnc2_ichar (tnc2)
register struct tnc *tnc2;

{
    char c;

    switch (tnc2->dev)
    {
    case 0:
    case 1:
    case 3:
    case 6:
    case 7:
	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 uchar(c);

    case 8:
	if (!Bconstat(1))		/* check RS232 (AUX) status */
	    break;

	return uchar(Bconin(1));	/* read char from RS232 (AUX) */

    case 9:
	if (!Bconstat(3))		/* check MIDI status */
	    break;

	return uchar(Bconin(3));	/* read char from MIDI */
    }

    return TCH_NOCHAR;
}

/* 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;

{
    int i;

    switch (tnc2->dev)
    {
    case 0:
    case 1:
    case 3:
    case 6:
    case 7:
	if (c < 0) {			/* a status check */
	    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? */
	    eihalt();				/* 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;

    case 8:
	i = 50;

	while (!Bcostat(1) && --i)	/* when output buffer is full */
	    eihalt();			/* wait a short while */

	if (i == 0)			/* tried it, but still not ready */
	    break;

	if (c >= 0)
	    Bconout(1,c);		/* send char to RS232 (AUX) */

	return 1;

    case 9:
	if (c >= 0)
	    Bconout(3,c);		/* send char to MIDI */

	return 1;
    }

    tnc2->status |= TS_TXOVR;		/* transmitter overrun */
    return 0;
}

/* set flowcontrol method for specified client */
/* method 1=soft (XON/XOFF) 2=hard (RTS/CTS) */
void
tnc2_flow (tnc2,method)
register struct tnc *tnc2;
int method;

{
    switch (tnc2->dev)
    {
    case 0:
    case 1:
    case 3:
    case 6:
    case 7:
	if (method & 1)			/* soft control used to set TX buffer */
	    tnc2->status |= TS_TXBUF;
	else
	    tnc2->status &= ~TS_TXBUF;

	break;

    case 8:
	if (biosvers < 0x0102)		/* when using old BIOS */
	    method &= ~2;		/* hardware flowctrl does not work */

	Rsconf(-1,method,-1,-1,-1,-1);	/* set RS232 flow control method */
	break;
    }
}
