/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * KL11 serial line controller
 *
 * arguments:
 *	ctrl
 *	  csr_base ivector ovector irq driver driver-args ...
 *	end
 */

# include "proc.h"

RCSID("$Id: dev_kl.c 454 2000-11-24 10:34:24Z hbb $")

# define NTTY	4			/* max number of lines per controller */
# define FIFO	1024			/* size of input buffer */
# define OUTBUF	1024			/* size of output buffer */
# define TFLUSH	60			/* msecs between output flushes */
# define TTYCSRMASK 017777770
# define TTYREGMASK (07)
# define CSR(x)	((x)&TTYCSRMASK)
# define REG(x) ((x)&TTYREGMASK)

# define D(C)	/* C */

typedef struct TTY TTY;
typedef	struct KL KL;

struct TTY {
	int		fd;

	int		tks;		/* keyb ready			*/
	u_char		tpb;

	int		tkint;		/* keyb interrupt enable 	*/
	int		tpint;		/* cons interrupt enable 	*/
	int		tkreq;
	int		tpreq;
	u_int		addr;		/* basis address	 	*/
	int		tkvec;		/* keyboard vector	 	*/
	int		tpvec;		/* print vector		 	*/
	int		ireq;		/* interrupt request level 	*/

	u_char		fifo[FIFO];	/* receiver queue 	 	*/
	int		ffree;		/* 1st free cell		*/

	u_char		obuf[OUTBUF];	/* transmitter buffer 		*/
	int		ofree;		/* next free byte		*/
};

struct KL {
	TTY		ttys[NTTY];
	int		ntty;
	int		osem;		/* for output synchronisation 	*/
	int		odo;		/* true, if something to output	*/

	/* statistics */
	int		ndrops;		/* how often we dropped input	*/
	int		bytes_dropped;	/* no of bytes dropped		*/
	int		ibytes;		/* input bytes			*/
	int		inputs;		/* no of reads			*/
	int		ofull;		/* output flushes on full buf	*/
	int		obytes;		/* output bytes 		*/
	int		outputs;	/* no of writes			*/
};

void	tty_ctrl_create(iodev_t *, int, char **);
void	tty_dev_create(iodev_t *, int, char **);
void	tty_ctrl_complete(iodev_t *);
void	tty_reset(iodev_t *);
u_short	tty_fetch(iodev_t *, u_int);
void	tty_store(iodev_t *, u_int, mmode_t, u_short);
u_short	tty_vector(iodev_t *);
void	tty_onsig(iodev_t *, void *);
void	tty_info(iodev_t *);
void	tty_flush(iodev_t *);

static int	ttyc_info(iodev_t *, int, char **);

iocmd_t tty_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			ttyc_info },
	{ NULL,		NULL,			NULL }
};

ioops_t	kl_ops = {
	tty_ctrl_create,	/* ctrl_create */
	tty_dev_create,		/* dev_create */
	tty_ctrl_complete,	/* ctrl_complete */
	tty_reset,		/* reset */
	tty_fetch,		/* fetch */
	tty_store,		/* store */
	tty_vector,		/* vector */
	0,			/* dma */
	tty_onsig,		/* async */
	tty_info,		/* info */
	tty_flush,		/* flush */
	tty_cmds,		/* cmds */
};

static void comp_req(iodev_t *);
static void set_tkreq(iodev_t *, TTY *);
static void shift_fifo(iodev_t *, TTY *);
static void tty_timer(void *);


void
tty_ctrl_create(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc)
		conf_panic("kl: to many args in controler configuration");

	dev->data = xalloc(sizeof(KL));
	(void)memset(dev->data, 0, sizeof(KL));
}

void
tty_dev_create(iodev_t *dev, int argc, char **argv)
{
	KL	*t = (KL *)dev->data;
	int	i;

	if(argc < 6)
		conf_panic("kl: device requires min 6 args");

	i = t->ntty++;
	if(t->ntty > 4)
		conf_panic("kl: cannot have more than 4 lines");

	t->ttys[i].fd = register_handler(argv[4], argv+4, dev, &t->ttys[i], -1, NULL);

	t->ttys[i].addr = parse_csr(argv[0], "kl");
	t->ttys[i].tkvec = parse_vec(argv[1], "kl");
	t->ttys[i].tpvec = parse_vec(argv[2], "kl");
	t->ttys[i].ireq = parse_irq(argv[3], "kl");
	proc.iopage[IOP(t->ttys[i].addr+0)] = dev;
	proc.iopage[IOP(t->ttys[i].addr+2)] = dev;
	proc.iopage[IOP(t->ttys[i].addr+4)] = dev;
	proc.iopage[IOP(t->ttys[i].addr+6)] = dev;
}

void
tty_ctrl_complete(iodev_t *dev)
{
	KL	*t = (KL *)dev->data;

	if(t->ntty > 0)
		register_timer(TFLUSH, tty_timer, dev);
}


static void
tty_timer(void *p)
{
	KL	*kl = (KL *)((iodev_t *)p)->data;

	if(kl->osem)
		return;
	tty_flush(p);
}

void
tty_flush(iodev_t *dev)
{
	KL	*kl = dev->data;
	TTY	*tty;

 	if(!kl->odo)
		return;
	for(tty = kl->ttys; tty < &kl->ttys[kl->ntty]; tty++) {
		if(tty->ofree) {
			write(tty->fd, tty->obuf, tty->ofree);
			kl->outputs++;
			kl->obytes += tty->ofree;
			tty->ofree = 0;
		}
	}
	kl->odo = 0;
}


/*
 * to get a signal on the next write into the write end
 * we need to read all of the pipe without blocking and
 * discard input that does not fit.
 */
void
tty_onsig(iodev_t *dev, void *argp)
{
	KL *kl = (KL *)dev->data;
	TTY *t = (TTY *)argp;
	int got;
	int drop;
	int newchars;
	char buf[1024];

	kl->inputs++;
	if((got = read(t->fd, buf, sizeof(buf))) < 0)
		panic("error reading pipe: %s", strerror(errno));
	else if(got == 0) {
		warn("kl: error reading pipe for tty%u: %s",
			t - kl->ttys, strerror(errno));
		return;
	}
	kl->ibytes += got;

	D(printf("got %d chars; ", got));
	if((drop = t->ffree + got - FIFO) > 0) {
		D(printf("dropping %d; ", drop));
		kl->ndrops++;
		kl->bytes_dropped += drop;
		if(drop < t->ffree) {
			memcpy(&t->fifo[0], &t->fifo[drop], t->ffree - drop);
			t->ffree -= drop;
		} else {
			t->ffree = 0;
		}
	}
	newchars = (got < FIFO - t->ffree) ? got : (FIFO - t->ffree);
	D(printf("new %d\n", newchars));
	memcpy(&t->fifo[t->ffree], &buf[got - newchars], newchars);
	t->ffree += newchars;
	set_tkreq(dev, t);
}

/*
 * if there are any buffered chars, request new interrupt
 */
static void
set_tkreq(iodev_t *dev, TTY *t)
{
	t->tks |= 0200;
	if(t->tkint & 0100) {
		IRQ(dev, t->ireq);
		t->tkreq++;
	}
}

void
tty_reset(iodev_t *dev)
{
	int 	i;
	KL	*t = dev->data;

	for( i = 0 ; i < t->ntty ; i++ ) {
		t->ttys[i].ffree = 0;
		t->ttys[i].fifo[0] = 0;
		t->ttys[i].tks = 0;
		t->ttys[i].tkint = 0;
		t->ttys[i].tpint = 0;
		t->ttys[i].tkreq = t->ttys[i].tpreq = 0;
		t->ttys[i].ofree = 0;
	}
	comp_req(dev);
}


u_short
tty_fetch(iodev_t *dev, u_int a)
{
	KL	*k = dev->data;
	TTY	*t;
	u_short 	v;

	for(t = k->ttys; t < &k->ttys[k->ntty]; t++)
		if(CSR(a) == t->addr)
			goto found;
	printf("tty_fetch(%o)\n", a);
	Trap4(0100);
	return v;
found:
	switch(REG(a)) {

	case 0:		/* TKS */
		v = t->tks | t->tkint;
		break;

	case 2:
		v = t->fifo[0];
		t->tks = 0;
		if(t->ffree != 0)
			shift_fifo(dev, t);
		break;

	case 4:
		v = t->tpint | 0200;
		break;

	case 6:
		v = t->tpb;
		break;

	default:
		printf("tty_fetch(%o)\n", a);
		Trap4(0100);
	}

	return v;
}

void
tty_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	KL	*t = dev->data;
	TTY	*k;
	int	n;

	for(n = 0; n < t->ntty; n++)
		if(CSR(a) == t->ttys[n].addr)
			goto found;
	printf("tty_store(%o)\n", a);
	Trap4(0100);
	return;

found:
	k = &t->ttys[n];

	switch(REG(a & ~1)) {

	case 0:		/* TKS */
		if(mode == M_High)
			break;
		if(!k->tkint && (v & 0100) && k->tks) {
			IRQ(dev, k->ireq);
			k->tkreq++;
		}
		if(!(k->tkint = v & 0100) && k->tkreq) {
			k->tkreq = 0;
			comp_req(dev);
		}
		break;


	case 2:
		break;


	case 4:
		if(mode == M_High)
			break;
		if(!k->tpint && (v & 0100)) {
			IRQ(dev, k->ireq);
			k->tpreq++;
		}
		if(!(k->tpint = v & 0100) && k->tpreq) {
			k->tpreq = 0;
			comp_req(dev);
		}
		break;


	case 6:
		if(mode == M_High)
			break;

		t->osem++;
		if(k->ofree >= OUTBUF) {
			t->outputs++;
			t->ofull++;
			t->obytes += OUTBUF;
			write(k->fd, k->obuf, OUTBUF);
			k->ofree = 0;
		}
		k->obuf[k->ofree++] = v;
		t->odo = 1;
		t->osem = 0;

		if(k->tpint) {
			IRQ(dev, k->ireq);
			k->tpreq++;
		}
		break;

	default:
		printf("tty_store(%o)\n", a);
		Trap4(0100);
	}
}


u_short
tty_vector(iodev_t *dev)
{
	u_short vec = 0;
	int	i;
	int	pending = 0;
	KL	*k = dev->data;
	int	irq = 0;

	for( i = 0 ; i < k->ntty ; i++ )
		if( k->ttys[i].tkreq) {
			if(!vec) {
				k->ttys[i].tkreq = 0;
				vec = k->ttys[i].tkvec;
				irq = k->ttys[i].ireq;
			}
			pending++;
		}

	for( i = 0 ; i < k->ntty ; i++ )
		if(k->ttys[i].tpreq) {
			if(!vec) {
				k->ttys[i].tpreq = 0;
				vec = k->ttys[i].tpvec;
				irq = k->ttys[i].ireq;
			}
			pending++;
		}
	dev->reqpri = 0;
	if( !pending )
		return 04;

	if(--pending)
		IRQ(dev, irq);
	return vec;
}

/*
 * if the receiver fifo was not empty, shift all cells down
 * and request new interrupt if nt empty after shifting
 */
static void
shift_fifo(iodev_t *dev, TTY *t)
{
	sigset_t nsig, osig;

	sigemptyset(&nsig);
	sigaddset(&nsig, SIGIO);
	sigprocmask(SIG_BLOCK, &nsig, &osig);

	if(--t->ffree > 0) {
		memcpy(&t->fifo[0], &t->fifo[1], t->ffree);
		set_tkreq(dev, t);
	}

	sigprocmask(SIG_SETMASK, &osig, NULL);
}

/*
 * recompute request word, this is needed if a pending
 * request is to be dropped by clearing the interrupt enable in the csr
 */
static void
comp_req(iodev_t *dev)
{
	KL *d = dev->data;
	TTY *k;

	dev->reqpri = 0;
	for(k = d->ttys; k < &d->ttys[d->ntty]; k++)
		if(k->tkreq || k->tpreq)
			IRQ(dev, k->ireq);
}


void
tty_info(iodev_t *dev)
{
	KL	*t = dev->data;
	TTY	*k;

	printf("KL serial line controller\n");
	printf("%d lines attached\n", t->ntty);

	printf("UNIT\tTKREQ\tTPREQ\tCSR\t\tVEC\n");

	for(k = t->ttys; k < t->ttys + t->ntty; k++)
		printf("%d\t%d\t%d\t%08o\t%03o/%d\n", (int)(k - t->ttys),
			k->tkreq, k->tpreq, k->addr, k->tkvec, k->ireq);

	printf("Statistics:\n");
	printf("input bytes:       %10d\n", t->ibytes);
	printf("  dropped:         %10d\n", t->bytes_dropped);
	printf("input reads:       %10d\n", t->inputs);
	printf("  with drops:      %10d\n", t->ndrops);
	printf("output bytes:      %10d\n", t->obytes);
	printf("output writes:     %10d\n", t->outputs);
	printf("  on buffer full:  %10d\n", t->ofull);
}

static int
ttyc_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	tty_info(dev);
	return 0;
}
