/*
 *	This file implements some sort of line discipline stuff using
 *	select and a separate process.
 *
 *	06/12/94, Kay Roemer.
 */

#include <osbind.h>
#include <mintbind.h>
#include <basepage.h>
#include <string.h>
#include "config.h"
#include "netinfo.h"
#include "kerbind.h"
#include "atarierr.h"
#include "file.h"
#include "util.h"
#include "if.h"
#include "buf.h"
#include "serial.h"

#define STKSIZE			8192L
#define NSLBUFS			4

#define FDZERO(fd)		((fd) = 0)
#define FDSET(fd, set)		((set) |= 1L << (fd))
#define FDCLR(fd, set)		((set) &= ~(1L << (fd)))
#define FDISSET(fd, set)	((set) & (1L << (fd)))

static short			glfd;
static char			*pname = "u:\\pipe\\sld";
static struct slbuf		*currsl, allslbufs[NSLBUFS];

#if 0
static void
kprintf (const char *fmt, ...)
{
	static char buf[200];
	extern int vsprintf (char *, const char *, char *);

	vsprintf (buf, fmt, ((char *)&fmt) + 4);
	Cconws (buf);
	Cconws ("\r\n");
}
#endif

static void
_send (void)
{
	(*currsl->send) (currsl);
}

static void
_recv (void)
{
	(*currsl->recv) (currsl);
}

/*
 * when calling Fcntl(fd, arg, cmd) directly gcc 2.5.8 does not detect that
 * *arg might be clobbered. Same for Supexec(). That is why we use those
 * wrapper functions instead ...
 */

static long
SafeFcntl (short fd, void *arg, short cmd)
{
	return Fcntl (fd, arg, cmd);
}

static long
SafeSupexec (void *arg)
{
	return Supexec (arg);
}

static void
_sld (void)
{
	struct slcmd cmd;
	register struct slbuf *sl;
	short i, pipefd, p, tmout = 0;
	long r, rset, wset, wready, rready, space;

	pipefd = Fopen (pname, O_RDONLY|O_NDELAY);
	if (pipefd < 0) {
		Cconws ("sld: PANIC: Cannot open pipe\r\n");
		return;
	}
	FDZERO (rset);
	FDZERO (wset);
	FDSET (pipefd, rset);

	for (;;) {
		wready = wset;
		rready = rset;
		/*
		 * Poll if some channels are active
		 */
		tmout = (rset & ~(1L << pipefd)) ? SL_VTIME : 0;
		r = Fselect (tmout, &rready, &wready, 0);
		if (r < 0) continue;

		if (FDISSET (pipefd, rready)) {
		    r = Fread (pipefd, 1, &cmd);
		    if (r == 1 && cmd.slnum < NSLBUFS) {
			sl = &allslbufs[cmd.slnum];
			if (sl->flags & SL_INUSE) switch (cmd.cmd) {
			case SLCMD_OPEN:
			    r = sl->fd;
			    sl->fd = Fdup (r);
			    Fclose (r);
			    if (sl->fd < 0) {
				Cconws ("sld: PANIC: Fdup failed\r\n");
				return;
			    }
			    FDSET (sl->fd, rset);
			    break;

			case SLCMD_CLOSE:
			    FDCLR (sl->fd, rset);
			    FDCLR (sl->fd, wset);
			    FDCLR (sl->fd, wready);
			    FDCLR (sl->fd, rready);
			    Fclose (sl->fd);
			    sl->flags &= ~(SL_INUSE|SL_SENDING|SL_CLOSING);
			    break;

			case SLCMD_SEND:
			    FDSET (sl->fd, wset);
			    FDSET (sl->fd, wready); /* pretend we can write */
			    currsl = sl;
			    SafeSupexec (_send);
			    break;
			}
		    }
		}
		for (i = 0; i < NSLBUFS; ++i) {
		    long nr;

		    sl = &allslbufs[i];
		    if ((sl->flags & (SL_INUSE|SL_CLOSING)) != SL_INUSE)
			continue;

		    /*
		     * We try to read something if
		     * 1) >= SL_VMIN chars arrived, or
		     * 2) no more input arrived in the last SL_VTIME msecs.
		     */
		    nr = 0;
		    SafeFcntl (sl->fd, &nr, FIONREAD);
		    if (FDISSET (sl->fd, rready) || (nr && nr <= sl->nread)) {
			p = sl->ihead;
			r = 1;
			if (p >= sl->itail) {
			    space = sl->isize - p;
			    if (sl->itail == 0) --space;
			    if (space > 0) {
				r = Fread (sl->fd, space, &sl->ibuf[p]);
				if (r > 0) {
		    		    p = (p + (short)r) & (sl->isize - 1);
		    		    nr -= r;
		    		}
			    }
			}
			if (r > 0 && p+1 < sl->itail) {
			    space = sl->itail - p - 1;
			    if (space > 0) {
				r = Fread (sl->fd, space, &sl->ibuf[p]);
				if (r > 0) {
				    p = (p + (short)r) & (sl->isize - 1);
				    nr -= r;
				}
			    }
			}
			if (r < 0)
			    Cconws ("sld: Fread failed\r\n");
			sl->ihead = p;
		    }
		    sl->nread = MAX (0, nr);
		    if (sl->ihead != sl->itail) {
			currsl = sl;
			SafeSupexec (_recv);
		    }

		    if (!(sl->flags & SL_SENDING))
			continue;

		    /*
		     * We send something if
		     * 1) connection was idle some time, or
		     * 2) space did not increase for SL_VTIME, or
		     * 3) can send whole buf, or
		     * 4) can send at least SL_VMIN
		     */
		    nr = 0;
		    SafeFcntl (sl->fd, &nr, FIONWRITE);
		    if (FDISSET (sl->fd, wready) || (nr && nr <= sl->nwrite) ||
			nr >= SL_VMIN || nr >= SL_OUSED (sl)) {
			FDSET (sl->fd, wset);
			p = sl->otail;
			r = 1;
			if (p > sl->ohead) {
			    space = sl->osize - p;
			    if (space > 0) {
				r = Fwrite (sl->fd, space, &sl->obuf[p]);
				if (r > 0) {
			    	   p = (p + (short)r) & (sl->osize - 1);
			    	   nr -= r;
			    	}
			    }
			}
			if (r > 0 && p < sl->ohead) {
			    space = sl->ohead - p;
			    if (space > 0) {
				r = Fwrite (sl->fd, space, &sl->obuf[p]);
				if (r > 0) {
				    p = (p + (short)r) & (sl->osize - 1);
				    nr -= r;
				}
			    }
			}
			if (r < 0)
			    Cconws ("sld: Fwrite failed\r\n");
			sl->otail = p;
			/*
			 * Produce more output if at least a quarter of
			 * our output buffer is free
			 */
			if (4*SL_OFREE (sl) >= sl->osize) {
			    if (sl->ohead == sl->otail) {
			        sl->flags &= ~SL_SENDING;
			        FDCLR (sl->fd, wset);
			    }
			    currsl = sl;
			    SafeSupexec (_send);
			    if (sl->ohead != sl->otail) {
			        sl->flags |= SL_SENDING;
			        FDSET (sl->fd, wset);
		            }
			}
		    }
		    sl->nwrite = MAX (0, nr);
		    /*
		     * If buffer is tiny then turn off this descriptor
		     * and wait up to SL_VMIN until next retry.
		     * The descriptor is turned on again after the
		     * next Fselect().
		     */
		    if (sl->nwrite < SL_OUSED (sl) && sl->nwrite < SL_VMIN)
			FDCLR (sl->fd, wset);
		}
	}
}

static int __CDECL
sld (register BASEPAGE *bp)
{
	setstack ((char *)bp + STKSIZE);

	bp->p_tbase = bp->p_parent->p_tbase;
	bp->p_tlen  = bp->p_parent->p_tlen;
	bp->p_dbase = bp->p_parent->p_dbase;
	bp->p_dlen  = bp->p_parent->p_dlen;
	bp->p_bbase = bp->p_parent->p_bbase;
	bp->p_blen  = bp->p_parent->p_blen;

	(void)Pdomain (1);
	(void)Pnice (-5);
	(void)Psigblock (-1L);
	_sld ();
	(void)Pterm (0);
	return 0;
}

long
serial_init (void)
{
	BASEPAGE *b;

	glfd = f_open (pname, O_NDELAY|O_WRONLY|O_CREAT|O_GLOBAL);
	if (glfd < 100) glfd += 100;
	f_chmod (pname, 0600);

	b = (BASEPAGE *)p_exec (PE_CBASEPAGE, 0L, "", 0L);
	m_shrink (0, b, STKSIZE + 256L);

	b->p_tbase = (char *)sld;
	b->p_hitpa = (char *)b + STKSIZE + 256L;

	return p_exec (104, "sld", b, 0L);
}

long
serial_make_raw (fd)
	short fd;
{
	short vmin[2];
	struct sgttyb sg;

	if (f_cntl (fd, &sg, TIOCGETP) < 0)
		return -1;
	sg.sg_flags &= ~(T_CRMOD|T_CBREAK|T_ECHO|T_XKEY|T_TOSTOP|T_EVENP|T_ODDP);
	sg.sg_flags |= T_RAW;
	if (f_cntl (fd, &sg, TIOCSETP) < 0)
		return -1;
	/*
	 * VTIME we (must) do ourselves...
	 */
	vmin[0] = SL_VMIN;
	vmin[1] = 0;
	if (f_cntl (fd, vmin, TIOCSVMIN) < 0)
		return -1;
	return 0;
}

struct slbuf *
serial_open (nif, device, send, recv)
	struct netif *nif;
	char *device;
	short (*send) (struct slbuf *);
	short (*recv) (struct slbuf *);
{
	struct slbuf *sl;
	struct slcmd cmd;
	short i;
	long r;

	sl = 0;
	for (i = 0; i < NSLBUFS; ++i) {
		if (!(allslbufs[i].flags & SL_INUSE)) {
			sl = &allslbufs[i];
			if (sl->ibuf) kfree (sl->ibuf);
			if (sl->obuf) kfree (sl->obuf);
			sl->ibuf = sl->obuf = 0;
		}
	}
	if (sl == 0) {
		DEBUG (("serial_open: out of sl bufs"));
		return 0;
	}

	sl->flags = SL_INUSE;
	sl->nif   = nif;
	sl->send  = send;
	sl->recv  = recv;
	sl->nread = 0;
	sl->nwrite= 0;
	sl->isize = SL_IBUFSIZE;
	sl->osize = SL_OBUFSIZE;
	sl->ihead = sl->itail = 0;
	sl->ohead = sl->otail = 0;
	sl->ibuf  = kmalloc (SL_IBUFSIZE);
	sl->obuf  = kmalloc (SL_OBUFSIZE);

	if (!sl->ibuf || !sl->obuf) {
		DEBUG (("serial_open: no mem for buffers"));
		if (sl->ibuf) kfree (sl->ibuf);
		if (sl->obuf) kfree (sl->obuf);
		sl->ibuf = sl->obuf = 0;
		sl->flags &= ~SL_INUSE;
		return 0;
	}

	strncpy (sl->dev, device, sizeof (sl->dev));
	sl->dev[sizeof (sl->dev) - 1] = '\0';

	sl->fd = f_open (sl->dev, O_RDWR|O_NDELAY|O_GLOBAL);
	if (sl->fd < 0) {
		DEBUG (("serial_open: fopen(%s) returned %d", sl->fd));
		kfree (sl->ibuf);
		kfree (sl->obuf);
		sl->ibuf = sl->obuf = 0;
		sl->flags &= ~SL_INUSE;
		return 0;
	}
	if (sl->fd < 100)
		sl->fd += 100;

	if (serial_make_raw (sl->fd)) {
		DEBUG (("serial_open: cannot make tty raw."));
		f_close (sl->fd);
		kfree (sl->ibuf);
		kfree (sl->obuf);
		sl->ibuf = sl->obuf = 0;
		sl->flags &= ~SL_INUSE;
		return 0;
	}

	cmd.slnum = sl - allslbufs;
	cmd.cmd   = SLCMD_OPEN;
	r = f_write (glfd, 1, &cmd);
	if (r != 1) {
		DEBUG (("serial_open: fwrite() returned %ld", r));
		f_close (sl->fd);
		kfree (sl->ibuf);
		kfree (sl->obuf);
		sl->ibuf = sl->obuf = 0;
		sl->flags &= ~SL_INUSE;
		return 0;
	}
	return sl;
}

long
serial_close (sl)
	struct slbuf *sl;
{
	struct slcmd cmd;
	long r;

	cmd.slnum = sl - allslbufs;
	cmd.cmd   = SLCMD_CLOSE;
	r = f_write (glfd, 1, &cmd);
	if (r != 1) {
		DEBUG (("serial_close: fwrite() returned %ld", r));
		return r ? r : -1;
	}
	sl->flags |= SL_CLOSING;
	return 0;
}

long
serial_send (sl)
	struct slbuf *sl;
{
	struct slcmd cmd;
	long r;

	if (sl->flags & SL_SENDING)
		return 0;

	cmd.slnum = sl - allslbufs;
	cmd.cmd   = SLCMD_SEND;
	r = f_write (glfd, 1, &cmd);
	if (r != 1) {
		DEBUG (("serial_send: fwrite() returned %ld", r));
		return r ? r : -1;
	}
	sl->flags |= SL_SENDING;
	return 0;
}
