static char rcsid[] = "$Header: kb.c,v 800.0 85/08/06 14:15:58 root Exp $";
static char sccsid[] = "%W% %Y% %Q% %G%";

#include "../h/param.h"			/* system types and definitions */
#include "../h/tty.h"			/* tty interface */
#include "../s32/vectors.h"		/* interrupt vector definitions */
#include "../s32/reentrant.h"		/* reentrant macro definition */
#include "../white/config.h"		/* IO base */
#include "../white/acia.h"		/* device (ACIA) control bits */
#include "../white/kb.h"		/* device (KB) definitions */


/*
 * Translation tables for keystrokes to shifted-characters,
 * keystrokes to regular-characters, and keystrokes to function-keys
 */
extern char kb_stab[];
extern char kb_ctab[];
extern char *kb_ftab[];
extern short kb_qtab[];
extern char kb_xtab[];	/* take characters to control characters */
#ifdef notdef	/* not yet */
extern struct kbflags {
	short bis;
	short bic;
} downflags[], upflags[];
#endif notdef
#ifdef notdef
extern int KEYUP;
extern int KEYDWN;
#endif notdef
extern char kb_ctlcode;	/* device-specific: stored with the keyboard tables */
extern struct tty co_tty[];

short	kb_flags = 0;	/* shift/ctl/lock or'd in here */
char	kb_chr = -1;	/* if repeating a character, this is the one */
char	*kb_ptr = 0;	/* if repeating a func-key, this is it */
char	kb_key;		/* "previous" -- to kill autorepeat */
char	kb_cnt;		/* for repeating */

#define KBSIZE	80
short	kb_spurious = 0;	/* Count of spurious interrupts: kbkeystroke */
short	kb_ov,
	kb_fe,
	kb_pe;
short	kb_debug;		/* Turn on interrupt character catch */
char	kb_buf[KBSIZE];		/* Circular buffer */
char	*kb_bufptr = kb_buf;	/* Pointer into buffer */

kbinit() 
{
	extern kbintr();

	IRQ4vector = kbintr;
}

kbopen() {
	register int d7;
	static frstime = 0;
	extern wakeup();
	extern kbintr();
	int i;
	int s;

	s = spl6();
	if (frstime == 0) {	/* first time is call by the kernel */
		frstime++;
		IRQ4vector = kbintr;		/* set up interrupt vector */
		KBADDR->ac_cmd = C_RIND;	/* turn off keyboard */
		KBADDR->ac_reset = 0;		/* reset UART */
		i = KBADDR->ac_data;		/* clear receive */
		KBADDR->ac_ctrl = kb_ctlcode;
		KBADDR->ac_cmd |= C_XIN3;	/* transmit a break */
#ifdef NOTYET
		timeout(wakeup, kbopen, 6);	/* 6*HZ is about 60 msec */
		sleep(kbopen, PZERO);
#endif NOTYET
		/* BUG the following assembly code is a kludge */
		/* just trying to get 60 ms of delay... */
		asm("	movl	#60000,d7	");
		asm("kbloop:			");
		asm("	tstb	d7		");
		asm("	dbf	d7,kbloop	");
		KBADDR->ac_cmd = C_DTR|C_XIN2;	/* turn on usart */
	} else if (frstime == 1) {	/* first file-open by user code */
		frstime++;
		kbtimer();
	}
	splx(s);
}


/* interrupt handler */
reentrant(kbintr) {
	kbkeystroke(0);
}


/*
 * Kernel mode getchar function.  Keep
 * looking for and processing key
 * transitions until we get a character
 * which we can return.
 */
kbgetchar() {
	register c;

	do {
		while ((KBADDR->ac_stat & S_RRDY) == 0)
			;
	} while ((c = kbkeystroke(1)) == -1);
	return(c);
}


#define PUTK(k) {\
	*kb_bufptr++ = (k);\
	if (kb_bufptr >= &kb_buf[KBSIZE])\
		kb_bufptr = kb_buf;\
}

/*
 * Get the keystroke (if one is present) and
 * do whatever is necessary.  If the flag kern
 * is set then we are called as a result of a
 * getchar() so we return the character or -1
 * if none is ready.  If the flag kern is not
 * set then we put any characters on the input
 * queue.  Note that function keys do not work
 * for kernel getchar().
 *
 * 841024 sbs
 *	This routine can now be called from the clock interrupt
 *	service.  The clock checks for break key, which involves
 *	reading the acia status register.  Reading the register
 *	unfortunately clears the IRQ line, thus squashing any
 *	pending input.  So, the clock service now detects when it
 *	has squashed the interrupt, and calls us with kern == 2.
 *	We stick the key into a short fifo and go on.
 *	Later when a real interrupt occurs, we empty the fifo first,
 *	then get the pending character.
 *	Note that if kern == 1, characters will come out of the
 *	fifo, but none go in (since we are at spl7).  Tough.
 *
 * Known bugs
 *	A character can get stuffed into the fifo and sit there for a
 *	long time, until a real key interrupt comes in.  One fix would be
 *	to have timeout code periodically read the fifo and call coinput.
 *	This would require 3 levels of control (key-intr, clock, and timeout),
 *	and probably a rewrite of this routine since it's getting kind of
 *	hairy.  Also, the general tty code should probably be rewritten so
 *	that it runs at timeout level, rather than at level 5.
 *
 *	We don't do exactly the right thing with ALT-LOCK.  There are 4
 *	key codes in an ALT-LOCK period:
 *		LED off + DOWN (turns on LED and ALT-LOCK)
 *		LED on  + UP   (does nothing)
 *		LED on  + DOWN (turns off LED and ALT-LOCK)
 *		LED off + UP   (does nothing)
 *	This sequence is modeled as
 *		FIRST-DOWN	(turns on ALT-LOCK)
 *		LAST-UP		(turns off ALT-LOCK)
 *	with the result that if we ever lose sync with the LED
 *	ALT-LOCK is off and the LED on and vice versa.  If you turn
 *	on ALT-LOCK and then type "reboot" you'll see what I mean.
 *
 *	This routine is getting too big for its britches.
 */
kbkeystroke(kern) {
	register unsigned char k, c;
	register isdownstroke;
	register char *p;
	static char kb_fifo[8];
	static short kb_f = 0, kb_l = 0;
	int	s;

	if (kern == 2) {	/* called by clock at level 6 */
		kb_fifo[kb_l] = KBADDR->ac_data;
		if (++kb_l >= sizeof(kb_fifo))
			kb_l = 0;
		return -1;
	}
top:
	s = spl6();		/* protect the fifo from clock jabs */
	c = KBADDR->ac_stat;
	if (c&S_RRDY) {
		kb_fifo[kb_l] = KBADDR->ac_data;
		if (++kb_l >= sizeof(kb_fifo))
			kb_l = 0;
	}
	if (kb_f == kb_l) {	/* happy ending */
		splx(s);
		return -1;
	}
	k = kb_fifo[kb_f];
	if (++kb_f >= sizeof(kb_fifo))
		kb_f = 0;
	splx(s);

	/*
	 * If debugging is on, drop the character code into the circular 
	 * buffer. Adjust the pointer.
	 */
	if (!kern && kb_debug)
		PUTK(k);

	if (k&STROKEBIT) {
		k &= ~STROKEBIT;
		isdownstroke = 1;
	} else
		isdownstroke = 0;
	c = kb_ctab[k];
	if (c >= QUL) {
		/*
		 * Change the qualifiers in kb_flags.  If it's a lock
		 * key (caps-lock or alt-lock), we only modify on
		 * downstrokes; otherwise, add or subtract the appropriate
		 * modifier flag.  We could use kb_flags ^= kb_qtab[c-QUL]
		 * but don't because this form is more forgiving of missed
		 * upstrokes.
		 */
#ifdef notdef
		register struct kbflags *kp =
		    &(isdownstroke? downflags: upflags)[c - QUL];
		
		kb_flags =| kp->bis;
		kb_flags =& kp->bic;
#endif notdef
		if (c == LCK1 || c == LCK2) {
			if (isdownstroke)
				kb_flags ^= KBLCK;
		} else if (c == ALCK1 || c == ALCK2) {
			if (isdownstroke)
				kb_flags ^= KBALCK;
		} else if (isdownstroke)
			kb_flags |= kb_qtab[c-QUL];
		else
			kb_flags &= ~kb_qtab[c-QUL];
	} else if (isdownstroke) {
		kb_cnt = 0; kb_key = k;
		if (kb_flags&KBSHFT)
			c = kb_stab[k];
#ifdef notdef	  /* enough wise guy stuff */
		if (c == HELP)		/* this won't last long... */
			printf("\r\nThanks! I could use some help!");
		else
#endif notdef
		if (c == PAUSE) {
			static pause_parity = 0;
			if (kern)
				return -1;
			else		/* provide stop/start characters */
				coinput((pause_parity ^= 1)?
				   co_tty[0].t_stopc: co_tty[0].t_startc);
		} else if (c < FUNCT) {	/* ordinary QWERTY key */
			if ((kb_flags&KBLCK) && c >= 'a' && c <= 'z')
				c += 'A' - 'a';
			if (kb_flags&KBCTL)
				c = kb_xtab[c];
			if (kern) {
				/* Clear the shift/alt/ctl flags */
				kb_flags &= ~(KBSHFT|KBALT|KBCTL);
				return(c);
			}
			kb_chr = c; kb_ptr = 0;
			coinput(c);
		} else if (c < FBTB) {	/* SCALD function key */
			static char mary[] = "~  \n";
/* SCALD function keys added 841031 (sbs) */
/* 841129 (sbs) mary[2] = use KBALT, not KBALT|KBALCK, to determine ALT key */
#define SCALDshift 1
#define SCALDctrl  2
			if (kern)
				return -1;
			mary[1] = 64+(kb_flags&KBSHFT? SCALDshift:0)+
					(kb_flags&KBCTL? SCALDctrl:0);
			mary[2] = ((kb_flags&KBALT)?',':' ')+c-F1;
			kb_chr = -1; p = kb_ptr = mary;
			while (*p)
				coinput(*p++);
		} else {		/* real function key */
			if (kern)
				return(-1);
			p = kb_ftab[c - FUNCT];
			kb_chr = -1; kb_ptr = p;
			while (*p)
				coinput(*p++);
		}
	} else {	/* key on the way back up */
		/* used to do this only if k == kb_key */
		kb_ptr = 0; kb_chr = -1;	/* not so horrible */
	}
	goto top;
}


/*
 * Timer routine for key-repeat.
 * HZ/KBTIME worth of waits is purportedly 1 second, which is too long.
 * So, trying some other values for the delay.
 */
short	kb_dorept = 0;		/* shut rept on/off */

kbtimer() {
	register char *p;
	static j = 0;

	if (kb_dorept) {
		if (kb_chr >= 0)
			if (kb_cnt < HZ/(KBTIME*2))
				kb_cnt++;
			else
				coinput(kb_chr);
		else if ((p = kb_ptr) != 0)
			if (kb_cnt < HZ/(KBTIME*2))
				kb_cnt++;
			else
				while (*p)
					coinput(*p++);
	}
	timeout(kbtimer, 0, KBTIME);
}
