/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:keyboard.c 12.0$ */
/* $ACIS:keyboard.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/cacons/RCS/keyboard.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:keyboard.c 12.0$";
#endif

/*
 * IBM RT PC Workstation keyboard (8051, 8255) driver.
 * also includes interrupt dispatch routine to mouse as well.
 */
#include "../h/param.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../h/systm.h"
#include "../h/uio.h"
#include "../h/file.h"
#include "../h/kernel.h"
#include "../machine/debug.h"

#ifdef SECURE
#include "../machine/nvram.h"
#endif SECURE

#include "../machinecons/screen_conf.h"
#include "../machinecons/consdefs.h"
#include "../machinecons/consvars.h"
#include "../machine/io.h"
#include "../machinecons/kls.h"
#include "../machinecons/keyboard.h"

#include "ms.h"

extern struct clist kbd_data;
unsigned long cons_sec;		/* time of last console keystroke */
char *kbd_set_ptr;
int kbd_set_cnt, kbd_set_out;
int kbd_led_flags;
int kbddone(),kbdstart(),kbd_led_done(),kbd_init_done(),kbd_swap_done();
struct kbdq	kbd_led_buf,kbd_init_buf,kbd_swap_buf;
struct kbdq	kbdq[KBDPOOLSZ];
struct kbdq	*kbdhead,*kbdtail;
struct klsq	*kbd_last;
struct kbdq	*kbdfree;
struct clist	kbd_swapq;
int kbd_swapq_status = 0;
int kbd_swapq_want = 0;
int click = 1;
int stdclick = 1;
char kbd_leds_state = 0;

#define MAXSCAN	255

/*
 * 'attribute' saves the {make,make/break,repeat} attribute of 
 * all the scan codes.  Higher nibble of the character is used to stored
 * the type of menu {standard,alternate}, and the lower nibble is used
 * to stored the lower nibble of the keyboard command that sets the 
 * attribute (since the higher nibble of the commands is always "F", e.g.
 * F6, F7, F8, F9, FA, FB, FC, and FD.)
 */
char attribute[MAXSCAN];	/* attribute for each scan code */
#define SET_ATTR(scan,menu,cmd) attribute[scan] = (menu << 4) | (cmd & 0x0F);

/*
 * the Keyboard wants to give us a character.
 */
/*ARGSUSED*/
kbdint()
{
	register int ch;
	register struct tty *tp = &cons[cons_if];
	register SCREEN_INFO *si = &cons_info[cons_if];

	DEBUGF(cndebug & 0x40, printf ("In CNRINT\n"));

	for (;;)
	{
		if ((ch = kls_read(kbd_data)) <= 0)
			break;
	
		DEBUGF(cndebug&0x80, printf ("scan code %x\n",ch));

#ifdef CONS_FOCUS
		if ((ch == cons_switch_focus_code) && (last_scan != 0xF0)) {
			cons_focus (SCREEN_SWITCH);
			last_scan = ch;
			continue;
		}
		last_scan = ch;
#endif CONS_FOCUS

		si->ine.intr_reason = E_INT_KBD;
		(*emulsw[si->ine.emulator].e_rint)(ch, tp, si);
	}

	DEBUGF(cndebug & 0x40, printf ("cnrint end\n"));
	cons_sec = time.tv_sec;		/* Last input time */
}

kbdreset()
{
	register SCREEN_INFO *si = &cons_info[cons_display];

#ifdef NOTIMEOUT
	getchar_timeout = 0;
#endif
	make_break = 0;
	klscmd(EXTCMD,0x30,0); 
	set_click(click);
	if (kbdhead != NULL)
		kbdstart();
#ifdef SECURE
	if (nvr_is_secure()) {
		printf("Secure bit detected, keyboard disabled.\n");
		kbd_state |= KBD_SECURE;
		klscmd(EXTCMD,KBDLOCK,0);
		kbd_init_buf.cmd[0].qp_cmd = NKSCAN; 
	} else {
		kls_raw_cmd(EXTCMD,NKBDLOCK);
#endif SECURE
		kbd_init_buf.cmd[0].qp_cmd = KSCAN;
		set_kbd_led(0);
#ifdef SECURE
	}
#endif !SECURE
	kbd_status(si,kbd_state);
	kbd_init_buf.count = 1;
	kbdstrategy(&kbd_init_buf);

	/* set the system attention sequence to <ctrl>+<alt><scroll-lock> */
	kls_raw_cmd((WRRAM | SR_SYSATTN_SCAN), MONO_SCROLL_LOCK);
	return (0);
}


#ifdef SECURE
kbd_enable(qp)
	register struct klsq *qp;
{
	register SCREEN_INFO *si = &cons_info[cons_display];

	if (qp->qp_ret & KBD_LOCKED)  {
		klsdone(qp);
		return;
	}
	klsdone(qp);
	kbd_init_buf.cmd[0].qp_cmd = KSCAN;
	kbd_init_buf.count = 1;
	kbdstrategy(&kbd_init_buf);
	klscmd(EXTCMD,NKBDLOCK,0);
	kbd_state &= ~KBD_SECURE;
	kbd_status(si,kbd_state);
}

kbd_lock_chg() 
{
	register int x, tmp;

	tmp = nvr_release();
	x = crc_gen(_nvr,sizeof(struct nvram)-NVR_CRC_CHECK_BYTES);
	nvr_set_crc(x);
	klscmd(EXTCMD,READ_STAT,kbd_enable);
}
#endif SECURE

set_kbd_led(leds)
char leds;
{
	register int s;

	if (leds <= 7)
		kbd_leds_state |= leds;		/* turn leds on */
	else
		kbd_leds_state &= leds;		/* turn leds off */

	s = KLSSPL();
	kbd_led_buf.cmd[0].qp_cmd=kbd_leds_state;
	if (kbd_led_flags & KBD_LED_QUEUED) 
		kbd_led_flags |= KBD_LED_WAIT;
	else {
		kbd_led_buf.count = 2;
		kbdstrategy(&kbd_led_buf);
		kbd_led_flags |= KBD_LED_QUEUED;
	}
	splx(s);
}

kbd_led_done(qp)
	register struct kbdq *qp;
{
	register int s = KLSSPL();

	if (kbd_led_flags & KBD_LED_WAIT) {
		kbd_led_buf.count = 2;
		kbdstrategy(&kbd_led_buf);
		kbd_led_flags &= ~KBD_LED_WAIT;
	} else
		kbd_led_flags = 0;
	splx(s);
}

kbdstrategy(qp)
	register struct kbdq *qp;
{
	register int s = KLSSPL();

	qp->qp_next = NULL;
	qp->ercnt = 0;
	if (kbdhead == NULL) {
		kbdhead = qp;
		kbdtail = qp;
		kbdstart();
	} else {
		kbdtail->qp_next = qp;
		kbdtail = qp;
	}
	splx(s);
}

kbddone(qp)
	register struct klsq *qp;
{
	kbd_last = qp;
	if (qp->qp_ret)
		kbdsint();
}

kbdsint()
{
	register struct klsq *qp=kbd_last;
	register struct kbdq *current;

	if (kbdhead == NULL)
		panic("kbdsint\n");

	/* Machine check encountered.. let kbdreset restart */
	if ((qp->qp_ret) == KLS_SOFT_ERROR) {
		kbdhead->count = kbdhead->maxcnt;
		return;
	}
	/* error, resend last command */
	if (qp->qp_ret == 0x41) {
		if (kbdhead->ercnt++ < 5)
			kbdstart();
		else
			klsreset();
		return;
	}

#ifdef SECURE
	if (qp->qp_ret == 0x42) {
		klscmd(EXTCMD,KBDLOCK,0);
		current=kbdhead;
		if ((kbdhead=kbdhead->qp_next) != NULL)
			kbdstart();
		(*current->free)(current);
		return;
	}
#endif SECURE

	if (qp->qp_ret)
		printf("kbdsint: 0x%x\n",qp->qp_ret);

	/* find next command. */
	if ((--kbdhead->count) <= 0) {
		current=kbdhead;
		if ((kbdhead=kbdhead->qp_next) != NULL)
			kbdstart();
		(*current->free)(current);
	} else
		kbdstart();
}

kbdstart()
{
	register int count;

	if (kbdhead == NULL)
		return;
	count = kbdhead->count;
	if ((count > 0) && (count <= kbdhead->maxcnt))
		klsstrategy(&kbdhead->cmd[count-1]);
	else 
		panic("kbdstart");
}

int current_menu = ALT_MENU;	/* At power up we are in Alternate menu */

/* Set all the keys to 'typecmd' type.  Then set the special set of
 * keys just to make/break so they don't repeat.
 */
set_default_key_types(typecmd, flag)
int typecmd;
int flag;
{
	int keys[20];
	register int i;

	/* Check if Repeat keys should be make/break or not */
	if (flag != current_menu) 
		tog_menu();
		
	/* Set all keys to typecmd */
	set_all_type(typecmd);

	for (i = 0; i <= MAXSCAN; i++) 
		SET_ATTR(i, current_menu, typecmd);
	
	/* Set the Control, Shift, etc... so they don't repeat repeat repeat..*/
	i = 0;
	/*  0 */ keys[i++] = MONO_SHIFT1;
	/*  1 */ keys[i++] = MONO_SHIFT2;
	/*  2 */ keys[i++] = MONO_ALT1;
	/*  3 */ keys[i++] = MONO_ALT2;
	/*  4 */ keys[i++] = MONO_CAPS_LOCK;
	/*  5 */ keys[i++] = MONO_CNTRL;
	/*  6 */ keys[i++] = MONO_PAUSE;
	/*  7 */ keys[i++] = MONO_PRINT;
	/*  8 */ keys[i++] = MONO_NUM_LOCK;
	/*  9 */ keys[i++] = MONO_SCROLL_LOCK;
	/* 10 */ keys[i++] = MONO_ESC;
	/* 11 */ keys[i++] = MONO_ACTION;
	/* 12 */ keys[i++] = 0;
	set_key_type(KSETMKBRK, keys);

	i = 0;
	/*  0 */ keys[i++] = CONS_SWITCH_FOCUS_CODE;
	/*  1 */ keys[i++] = 0;
	set_key_type(KSETMAKE, keys);
}


kbdqfree(qp)
	register struct kbdq *qp;
{
	/*
	 * In error conditions the commands may not have been
	 *  executed.
	 */
	qp->count = 0;
	wakeup((caddr_t) qp);
}

tog_menu ()
{
	set_all_type(KTOGMENU);

	/* wait for command complete */
	current_menu = !current_menu;
}

set_all_type(typecmd)
register int typecmd;
{
	register struct kbdq *qp;
	register int s = KLSSPL();

	while ((qp = (struct kbdq *) klsalloc(&kbdfree)) == NULL)
		sleep ((caddr_t) &kbdfree,KBDPRI);

	qp->count = 1;
	qp->cmd[0].qp_dest = KYBDCMD;
	qp->cmd[0].qp_cmd = typecmd;
	qp->cmd[0].qp_callback = kbddone;
	qp->maxcnt = 1;
	qp->free = kbdqfree;
	kbdstrategy(qp);
	while (qp->count != 0)
		sleep((caddr_t) qp,KBDPRI);
	klsfree(kbdfree,qp);
	wakeup((caddr_t) &kbdfree);
	splx(s);
}


/* 'typecmd' is what type of key you want to set the array of key codes 'keys'
 * to.  The last element in the 'keys' array should be a 0
 */
set_key_type(typecmd, keys)
register int typecmd;
register int *keys;
{
	register struct kbdq *qp;
	register int s = KLSSPL();

	while ((qp = (struct kbdq *) klsalloc(&kbdfree)) == NULL)
		sleep ((caddr_t) &kbdfree,KBDPRI);

	while (*keys) {
		for(qp->maxcnt = 0;qp->maxcnt < KBDMAXCOUNT-1;qp->maxcnt++) {
		    qp->cmd[qp->maxcnt].qp_dest=KYBDCMD;
		    qp->cmd[qp->maxcnt].qp_cmd = *keys;
		    qp->cmd[qp->maxcnt].qp_callback = kbddone;
		    SET_ATTR(*keys, current_menu, typecmd);
		    if ((*++keys) == 0) {
			qp->maxcnt++;
			break;
		    }
		}
		qp->cmd[qp->maxcnt].qp_dest = KYBDCMD;
		qp->cmd[qp->maxcnt].qp_cmd = typecmd;
		qp->cmd[qp->maxcnt].qp_callback = kbddone;
		qp->count = (++qp->maxcnt);
		qp->free = kbdqfree;
		kbdstrategy(qp);
		while (qp->count > 0)
			sleep((caddr_t) qp,KBDPRI);
	}
	klsfree(kbdfree,qp);
	wakeup((caddr_t) &kbdfree);
#ifdef SECURE
	if (nvr_is_secure()) {
		kbd_init_buf.cmd[0].qp_cmd = NKSCAN; 
	} else {
		kbd_init_buf.cmd[0].qp_cmd = KSCAN;
	}
#else !SECURE
	kbd_init_buf.cmd[0].qp_cmd = KSCAN;
#endif !SECURE
	kbd_init_buf.count = 1;
	kbdstrategy(&kbd_init_buf);
	splx(s);
}


/* Set key click duration */
click_duration(n)
unsigned char n;
{
	register int s = KLSSPL();
	klscmd((WRRAM | SR_CLICKDUR),n,0);
	splx(s);
}

/* Handy procedure for giving extended commands */
keyextcmd(n)
unsigned char n;
{
	register int s = KLSSPL();
	klscmd(EXTCMD,n,0);
	splx(s);
}
	

int beep_time = SPTIME;
int beep_freqh = FCHI;
int beep_freql = FCLO; 
int beep_vol = CLICKVOL;

beep()
{
	speaker(beep_vol, beep_freqh, beep_freql, beep_time);
}


#ifdef KBDREBOOT
_reboot()
{
	/*
	 * generate a reboot request via the keyboard interface 
	 */
	TRACEF(("reboot in progress..."));
#ifndef NOCONSOLEKEY
	(void) kls_raw_cmd(EXTCMD,0x3a);	/* turn on diagnostic mode */
	(void) kls_raw_cmd(EXTCMD,0x61);	/* request soft reset */
	(void) kls_raw_cmd(EXTCMD,0x2a);	/* turn off diagnostic mode */
#else /* NOCONSOLEKEY */
	(void) kls_must_cmd(EXTCMD,0x3a);	/* turn on diagnostic mode */
	(void) kls_must_cmd(EXTCMD,0x61);	/* request soft reset */
	(void) kls_must_cmd(EXTCMD,0x2a);	/* turn off diagnostic mode */
#endif /* NOCONSOLEKEY */
	delay(5000);				/* should be long enough */
	/* NOTREACHED */
	TRACEF(("reboot failed\n"));	/* can't get here ! */
}
#endif /* KBDREBOOT */

/*
 * if OPEN (normal case) then send a SIGHUP, otherwise a SIGKILL.
 * in any case send a SIGCONT, flush the input/output queues and
 * turn off carrier (which should result in TS_ISOPEN going away).
 */
cnhangup()
{
#ifdef KERNEL
	register struct tty *tp = &cons[cons_if];
	if (tp->t_state&TS_ISOPEN) {
		gsignal (tp-> t_pgrp, (tp->t_state & TS_CARR_ON) ? SIGHUP : SIGKILL);
		gsignal (tp-> t_pgrp, SIGCONT);
		ttyflush(tp,FREAD|FWRITE);
		tp->t_state &= ~ TS_CARR_ON;
	}
#endif KERNEL
}

/*
 * if we are stopped then simulate a start character,
 * otherwise simulate a stop character.
 */
cnscroll()
{

	register struct tty *tp = &cons[cons_if];
	register int c = (tp->t_state&TS_TTSTOP) ? tp->t_startc : tp->t_stopc;
#ifdef KERNEL
	if (c)
		(*linesw[tp->t_line].l_rint)(c,tp);
#endif
}


kbdinit()
{
	register struct kbdq *qp = &kbd_led_buf;
	/*
	 * If we have a generic kernel, we may need the keyboard before the
	 * console has been opened. In that case we need to call the kbdcreset
	 * here.
	 */
#ifdef GENERIC
	kbdcreset();
#endif
	_init_kbd++;
	kbdfree= (struct kbdq *) klsminit(kbdq,sizeof(struct kbdq),KBDPOOLSZ);
	qp->cmd[1].qp_dest = KYBDCMD;
	qp->cmd[1].qp_cmd  = SETLED;
	qp->cmd[1].qp_callback = kbddone;
	qp->cmd[0].qp_dest = KYBDCMD;
	qp->cmd[0].qp_cmd = 0;
	qp->cmd[0].qp_callback = kbddone;
	qp->maxcnt = 2;
	qp->free = kbd_led_done;
	kbd_led_flags = 0;
	qp = &kbd_init_buf;
	qp->cmd[0].qp_dest = KYBDCMD;
	qp->cmd[0].qp_cmd = KSCAN;
	qp->cmd[0].qp_callback = kbddone;
	qp->maxcnt = 1;
	qp->free = kbd_init_done;
	kbd_state = 0;
}	

/* ARGSUSED */
kbd_init_done(kbdqp)
	register struct kbdq *kbdqp;
{}


kbd_swap_attribute(scan1, scan2)
int scan1, scan2;
{
	register  scan = scan1;
	register  int  typecmd, menu;
	int	  new_attr = attribute[scan2];
	int	  attr1 = attribute[scan1];
	int	  i;

	for (i = 0; i < 2; i++) {
		typecmd = 0xF0 | (new_attr & 0x0F);
		menu = new_attr >> 4;

		/* This 'if' statement is used to convert a keyboard command 
		 * in the "all keys" set to the "subsequent list of keys"
		 * set, if applicable.  The conversion is:
		 *	"All keys" cmd		"List of keys" cmd
		 *	    F9	     ===>		FD	
		 *	    F8       ===>		FC
		 *          F7	     ===>		FB
		 *          F6	     ===>		FB, HAVE_REPEAT_MKBRK
		 *
		 * See page 9-5 of the RT PC Hardware Tech. Ref.
		 */
		if (typecmd > KDEAFULT && typecmd < KSETDBRPT)
			typecmd += 4;
		else if (typecmd == KDEAFULT) {
			typecmd = KSETRPT;
			menu = HAVE_REPEAT_MKBRK;
		}

		/* Check if Repeat Keys should be make/break or not */
		if (menu != current_menu) {
			kbd_enqueue_cmd(KTOGMENU, 0);
			current_menu = !current_menu;
		}
		kbd_enqueue_cmd(typecmd, scan);
		scan = scan2;
		new_attr = attr1;
        }
#ifdef SECURE
                if (nvr_is_secure()) {
                        typecmd = NKSCAN;
                } else {
                        typecmd = KSCAN;
                }
#else !SECURE
                typecmd = KSCAN;
#endif !SECURE
                kbd_enqueue_cmd(typecmd, 0);
}

kbd_enqueue_cmd(typecmd, key)
register int typecmd, key;
{
	register int s = KLSSPL();

	if (!(kbd_swapq_status & KBD_BUSY)) {
		kbd_swapq_status |= KBD_BUSY;
		if (key == 0) {
                        kbd_swap_buf.cmd[0].qp_dest = KYBDCMD;
                        kbd_swap_buf.cmd[0].qp_cmd  = typecmd;
                        kbd_swap_buf.cmd[0].qp_callback = kbddone;
                        kbd_swap_buf.count = kbd_swap_buf.maxcnt = 1;
			kbd_swap_buf.free = kbd_swap_done;
		} else {
                        kbd_swap_buf.cmd[0].qp_dest = KYBDCMD;
                        kbd_swap_buf.cmd[0].qp_cmd = key;
                        kbd_swap_buf.cmd[0].qp_callback = kbddone;
		        SET_ATTR(key, current_menu, typecmd);
			kbd_swap_buf.cmd[1].qp_dest = KYBDCMD;
                        kbd_swap_buf.cmd[1].qp_cmd = typecmd;
                        kbd_swap_buf.cmd[1].qp_callback = kbddone;
                        kbd_swap_buf.count = kbd_swap_buf.maxcnt = 2;
			kbd_swap_buf.free = kbd_swap_done;
		}
  		kbdstrategy(&kbd_swap_buf);
	} else {
		putc((char)(typecmd & 0xFF), &kbd_swapq);
		if (key != 0)
			putc((char)(key & 0xFF), &kbd_swapq);
		kbd_swapq_want++;
	}
	splx(s);
}

kbd_swap_done(qp)
register struct kbdq *qp;
{
	register char typecmd;
	register char key;
	register int s = KLSSPL();

	kbd_swapq_status &= ~KBD_BUSY;
	if (kbd_swapq_want) {
		typecmd = getc(&kbd_swapq);
		if (typecmd == KTOGMENU || typecmd == KSCAN || 
		    typecmd == NKSCAN)
			key = 0;
		else {
			key = getc(&kbd_swapq);
		}
		kbd_swapq_want--;
		kbd_enqueue_cmd(typecmd, key);
	}
	splx(s);
}

kbdwait()
{
	while (kbdhead != NULL)
		delay(1);		/* for hc2 */
}
