/*
 *	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			1024L
#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];

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

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

static void
_sld (void)
{
	struct slcmd cmd;
	register struct slbuf *sl;
	short i, pipefd, p;
	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;
		r = Fselect (0, &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;
			    Supexec (_send);
			    break;
			}
		    }
		}
		for (i = 0; i < NSLBUFS; ++i) {
		    sl = &allslbufs[i];
		    if ((sl->flags & (SL_INUSE|SL_CLOSING)) != SL_INUSE)
			continue;

		    if (FDISSET (sl->fd, rready)) {
			p = sl->ihead;
			r = 1;
			if (p >= sl->itail) {
			    space = sl->size - 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->size - 1);
			    }
			}
			if (r > 0 && p < 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->size - 1);
			    }
			}
			sl->ihead = p;
			currsl = sl;
			Supexec (_recv);
		    }
		    if (FDISSET (sl->fd, wready)) {
			p = sl->otail;
			r = 1;
			if (p > sl->ohead) {
			    space = sl->size - p;
			    if (space > 0) {
				r = Fwrite (sl->fd, space, &sl->obuf[p]);
				if (r > 0)
			    	   p = (p + (short)r) & (sl->size - 1);
			    }
			}
			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->size - 1);
			    }
			}
			sl->otail = p;
			if (sl->ohead == sl->otail) {
			    sl->flags &= ~SL_SENDING;
			    FDCLR (sl->fd, wset);
			}
			currsl = sl;
			Supexec (_send);
			if (sl->ohead != sl->otail) {
			    sl->flags |= SL_SENDING;
			    FDSET (sl->fd, wset);
		        }
		    }
		}
	}
}

static int
sld (bp)
	register long bp;
{
	setstack (bp + STKSIZE);
	(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);
}

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->size  = SL_BUFSIZE;
	sl->ihead = sl->itail = 0;
	sl->ohead = sl->otail = 0;
	sl->ibuf  = kmalloc (SL_BUFSIZE);
	sl->obuf  = kmalloc (SL_BUFSIZE);

	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;

	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;
}
