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

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

/* 
 *
 * 8042 scheduler. Controls access to the planar 8042 keyboard/mouse controller.
 *
 */
#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../machine/io.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../machinecons/screen_conf.h"
#include "../machinecons/consdefs.h"
#include "../machinecons/consvars.h"
#include "../machine/debug.h"
#include "../ca_atr/kls.h"
#include "../pc_code/vga.h"

/*
 *  In the PS/2 system ROMP cannot access PC keyboard I/O ports
 *  since they are below 0x100. So, the PC will do all keyboard I/O
 *  putting scan codes into PC memory so ROMP can access them.  The
 *  address of this kbdata area is stored in the cbcb.
 */
#include "../ca_atr/pcif.h"	/* need cbcb to get data from keyboard/mouse */
#include "ms.h"

struct kbdata  *kbdata;		/* ptr to keyboard data area in PS/2 memory */
int		kb_pc_cb;	/* actual PS/2 address of keyboard data */
int		ms_pc_cb;	/* actual PS/2 address of mouse data */
int		sp_pc_cb;	/* actual PS/2 address of mouse data */

#if NMS > 0
struct msdata  *msdata;		/* ptr to mouse data area in PS/2 memory */

struct pcms_cmd pcms_cmd;	/* mouse command structure	 */
#endif NMS
struct spkdata *spkdata;	/* ptr to mouse data area in PS/2 memory */

int             klsdone();
caddr_t         klsminit();
struct klsq    *klsalloc();
struct clist    kbd_data, ms_block, ms_uart;	/* character "queues" */
struct klsq    *klshead, *klstail;	/* pointers to the xmit queues */
struct klsq    *klsfreel;	/* free list */
struct klsq     klspool[KLSPOOLSZ];
int             klsdebug = 0;
int             klsinreset = 0;

/*	ROMP->PC IO Port Addresses	*/

int             kls_cntir;
int             kls_cntiw;
int             kls_rd;
int             kls_wrt;
int             at_int_mask;

#ifdef DEBOUNCE
int             klslastscan = 0x100;
struct timeval  klslasttime = {0, 0};
#endif DEBOUNCE

/* These are things that happen only once during boot time */
klsinit()
{

	klshead = NULL;
	klsfreel = (struct klsq *) klsminit(klspool, sizeof(struct klsq), KLSPOOLSZ);

	/*
	 * If the PC is handling the keyboard then we must get the PC address
	 * of the kbdata structure from the cbcb. 
	 */
	register int    old_window = get_512_window();	/* save the current
							 * window */
	register int    i;
	u_char          old_fwd_int;

	set_512_window(cbcb_addr);	/* point to the cbcb */
	kb_pc_cb = cbcb->cbcb_ent[KBENT].pc_cb;
	ms_pc_cb = cbcb->cbcb_ent[MSENT].pc_cb;
	sp_pc_cb = cbcb->cbcb_ent[SPKENT].pc_cb;

	kbdata = (struct kbdata *) (set_512_window(kb_pc_cb) + pcif_512_fw);

	old_fwd_int = kbdata->fwd_int;	/* save interrupt forwarding flag */
	kbdata->fwd_int = 0;	/* turn OFF kbd interrupt forwarding from PC */

	for (i = 0; i < 20; i++) {	/* clear pending scan codes */
		kbdata->kbstatus = 0;
		delay(1);
	}
	kbdata->fwd_int = old_fwd_int;	/* restore interrupt forwarding flag */

#if NMS > 0
	/* set mouse data ptr also */
	msdata = (struct msdata *) (set_512_window(ms_pc_cb) + pcif_512_fw);
	msdata->status = 0;	/* clear any mouse data */
#endif NMS
	spkdata = (struct spkdata *) (set_512_window(sp_pc_cb) + pcif_512_fw);
	spkdata->status = 0;	/* clear any mouse data */

	kbdinit();
#if NMS > 0
	msinit();
#endif NMS
	spkinit();
	klsreset();		/* This resets everyone */
#ifndef AT
	/*
	 * if we're not using an old type pc_code, tell the pc_code that
	 * we are ready to receive hot key events. This if may let us
	 * remove the @ifdef AT, but I don't have an AT to test it.
	 */
	if (config.os_type != OLD_TYPE) {
		unsigned vga_win = get_pc_cb(BIOSENT);
		struct vga_params *vga = (struct vga_params *)
					(set_512_window(vga_win)+pcif_512_fw);
		unsigned long timeout = 1000000; /* is this timeout too long?? */

		vga->unix_opcode = VGA_HKEY_REQ;
		pc_req(CB_BIOSREQ, (char *)0, BIOSENT);
		while (vga->unix_opcode && --timeout) 
			delay(1);
		if (!timeout) {
			/* should this be a panic? */
			printf("can't set hotkey!!!!!!\n");
		}
	}
#endif AT
	set_512_window(old_window);	/* restore the window */
}



kls_raw_read(iid)
	register int   *iid;
{
	register int    c;
	register        timeout = KLSMAXTIME;

	register int    old_window;
	u_char          save_flag;
	/*
	 * If the PC is handling the keyboard then we must poll the kbdata
	 * area in PC memory for key scan codes. 
	 */
	old_window = get_512_window();	/* Save the current window */
	set_512_window(kb_pc_cb);	/* Address the keyboard data */

	save_flag = kbdata->fwd_int;	/* save interrupt forwarding flag */
	kbdata->fwd_int = 0;	/* turn OFF kbd interrupt forwarding from PC */

	/* wait for something */
	while (((*iid = (int) kbdata->kbstatus) == 0) && (timeout-- > 0))
		 delay(1);
	if (timeout <= 0) {
		*iid = KLS_SOFT_ERROR >> 8;
		kbdata->fwd_int = save_flag;	/* restore flag */
		set_512_window(old_window);	/* restore the window */
		return (KLS_SOFT_ERROR & 0xff);
	}
	c = (int) kbdata->kbsc;		/* read scan code	 */
	*iid = (int) kbdata->kbstatus;	/* remember if scan/ascii */

	kbdata->kbstatus = 0;		/* inform the PC	 */

	kbdata->fwd_int = save_flag;	/* restore interrupt forwarding flag */
	set_512_window(old_window);	/* restore the window */

	*iid &= IID_MASK;
	DEBUGF(klsdebug, printf("kls_raw_read: iid=%x c=0x%x\n", *iid, c));
	return (c);
}


/*	Kls_pflush trys to clear the input buffer. by doing successive reads. */
static kls_pflush(count)
	register int    count;
{
}

/* following allows us to get rid of bell and keyboard clicks easily */
#ifndef NOBELL
#define NOBELL 0
#endif

int             nobell = NOBELL;

klsreset()
{
	register int    s = KLSSPL();	/* don't let anyone interupt us! */

	kbd_ints(NO);		/* Disarm keyboard OBF interrupt  */

	if (klshead != NULL) {	/* commands have been queued */
		/* clear queue & wake everyone up */
		while (klshead != NULL) {
			klshead->qp_ret = KLS_SOFT_ERROR;
			if (klshead->qp_callback)
				(*klshead->qp_callback) (klshead);
			klshead = klshead->qp_next;
		}
		/* flush out pipeline */
		kls_pflush(10);
	}


	/* put a dummy pointer on the queue to prevent kls from starting */
	/* if individual reset routines queue a command */
	klsinreset = 1;

	/*
	 * The following routines have full control over the adapter as long
	 * as they don't enable unsolicited input. They may place a command
	 * on the queue to do so. These routines are also responsible for any
	 * special cleanup caused by the rejection of the commands commands
	 * before the reset. 
	 */

	spkreset();
	kls_pflush(4);		/* clean up any strays */
#if NMS > 0
	msreset();
	kls_pflush(4);		/* clean up any strays */
#endif NMS
	kbdreset();
	kls_pflush(4);

	beep();			/* beep AFTER speaker has been inited! */

	/* start commands that have been queued */
	klsinreset = 0;
	if (klshead != NULL) {
		klsstart();
	}
	splx(s);
}


/* ARGSUSED */
klsint(unit, irq, info)
	int             unit;
	int             irq;
	int             info;
{
#if NMS > 0
	register u_char *msp;
#endif NMS
	register int    c;
	register int    iid;
	register int    klsret = KLSINVRET;
	register int    not_our_int = 1;
	register int    kbdflag = 0;	/* kbd data flag */
	register int    spkflag = 0;
	register int    x, i;
	static int      lastscan = -1;	/* the last scan code */
/*
	int spkr = 0;
	int kbdsend = 0;
*/

	int             old_window;

	/* For PCKBD ops we must get data from cbcb */

	old_window = get_512_window();	/* Save the current window */
	set_512_window(ms_pc_cb);	/* Address the mouse data */

	switch (IRQ_GET_INFO(info)) {

#if NMS > 0
	case KLS_MOUSE_INTR:
		if (msdata->status == 0) {
			DEBUGF(klsdebug,printf("Stray mouse interrupt\n"));
			set_512_window(old_window);	/* restore the window */
			return(1);
		}
		switch (msdata->type) {
		case MS_DATA:	/* mouse data report */
			msp = &msdata->report[0];	/* set pointer to data */
			for (i = 0; i <= 2; i++)	/* put 3 bytes on mouse
							 * clist */
				putc(((int) (*msp++) & 0xff), &ms_uart);
			msdata->status = 0;	/* inform PS/2 */
			msrint();
			break;

		case MS_CMDRESP:	/* mouse command response */
			/*
			 * klsret will be passed to klssendint(). For now
			 * just handle cmd exit status and ignore any bytes
			 * sent by request. exit_stat = (CY) << 8 ||
			 * (BIOS_RETURN) 
			 */
			klsret = (int) (((msdata->exit_stat & 0xff) << 8) | (msdata->error));
			msdata->status = 0;	/* inform PS/2 */
			klssendint(klsret);
			break;
		}
		set_512_window(old_window);	/* restore the window */
		return(0);
#endif NMS


	case KLS_SPEAKER_INTR:
		if (spkdata->status == 0) {
			DEBUGF(klsdebug,printf("Stray speaker interrupt\n"));
			set_512_window(old_window);	/* restore the window */
			return(1);
		}
		spkdata->status = 0;	/* inform PS/2 we got it */
		spkrint();
		set_512_window(old_window);	/* restore the window */
		return(0);

	case KLS_KEYBOARD_INTR:
		if ((iid = ((int) kbdata->kbstatus)) == 0) {
			DEBUGF(klsdebug,printf("Stray speaker interrupt\n"));
			set_512_window(old_window);	/* restore the window */
			return (1);
		}
		DEBUGF(klsdebug&0x04,
			 printf("klsint: kbdata->kbstatus=%x kbsc=%x\n",
					 kbdata->kbstatus, kbdata->kbsc));
		c = (int) kbdata->kbsc & 0xff;	/* read scan code */
		kbdata->kbstatus = 0;		/* inform PS/2 we got it */
		/* pass along ASCII byte */
		switch (iid) {
		case KBD_ASCII:
			ttyinput(c, &cons[cons_if]);
			set_512_window(old_window);
			return(0);
		case KBD_CPUSTATE:
			{
			int altcpustate();
			timeout(altcpustate,c,0);
			}
			set_512_window(old_window);
			return(0);
#ifndef AT
		case KBSTAT_HOT_KEY:
			/*
			 * hotkey call to save the screens.
			 * we assume saving the vga really means saving
			 * everything. have screen_save and screen_restore
			 * deal with it.
			 */
			if (c == VGA_SAVE ) {
				c = screen_all_save();
			} else {
				c = screen_all_restore();
			}
			/* if we saved everything then notify the other side */
			if (!c) {
				unsigned vga_win = get_pc_cb(BIOSENT);
				struct vga_params *vga = (struct vga_params *)
					(set_512_window(vga_win)+pcif_512_fw);
				/* clear the opcode to tell the pc_code we're done */
				vga->pc_opcode = 0;
				
			}
			set_512_window(old_window);
			return(0);
#endif /* AT */
		default:
			/*
		 	 * Determine source of keyboard interrupt by
		 	 * examining scan code in c.  If 0<c<=0x84 (valid
			 * make codes) or c=0xf0 (break code), then it was a
			 * keystroke (unless it is 0xaa which we can get when
			 * the keyboard is disconnected) Else, assume return
			 * code from keyboard command. 
			 */

			if (c == KBR_ERROR)	/* Keyboard Error = 0 */
				x = KLS_ERROR;
			else if ((c > 0x84) && (c != 0xf0) && (c != 0xaa)) {
				x = KLS_REQ;
			} else {
				x = KLS_KBD;
			}

			switch (x) {
			case KLS_KBD:
				/* XXX need to check if we run out of clists */
#ifdef DEBOUNCE
				if (c != klslastscan) {
					putc(c, &kbd_data);
					kbdint();
				}
				klslastscan = c;
#else
				putc(c, &kbd_data);
				kbdint();
#endif DEBOUNCE
				break;
			case KLS_REQ:
				DEBUGF(klsdebug, 
				  printf("KLSINT: kbd resp (0x%x)\n", c););
				klsret = (iid << 8) + c;
				klssendint(klsret);
				
				break;
			case KLS_ERROR:
				if (c != lastscan)
					klserror(lastscan);
				break;
			default:
				printf("klsint: received unexpected iid = %x\n",
								 iid);
			}
			lastscan = c;	/* to "debounce" errors */

			set_512_window(old_window);	/* restore the window */
			return(0);
		}
		
	default:
		printf("INVALID kbd info %d\n",IRQ_GET_INFO(info));
		/* fall through */
	case 0:	
	/*
 	 * this is for old pc code compatibility
	 */
#if NMS > 0
		/* check for mouse data first */
		if (msdata->status == 1) {
			not_our_int = 0;
			DEBUGF(klsdebug&0x02,
			  printf("klsint: msdata->status=%x\n", msdata->status));
			switch (msdata->type) {
			case MS_DATA:	/* mouse data report */
				msp = &msdata->report[0]; /* set pointer to data 
							   */
				for (i = 0; i <= 2; i++)  /* put 3 bytes on mouse
							   * clist */
					putc(((int) (*msp++) & 0xff), &ms_uart);
				break;

			case MS_CMDRESP:	/* mouse command response */
				/*
				 * klsret will be passed to klssendint().For now
				 * just handle cmd exit status and ignore any 
				 * bytes sent by request. exit_stat = 
				 * (CY) << 8 || (BIOS_RETURN) 
				 */
				klsret = (int)(((msdata->exit_stat & 0xff) << 8)
							 | (msdata->error));
				break;
			}
			msdata->status = 0;	/* inform PS/2 */
		}
#endif NMS

		/* now check for keyboard data */
		if ((iid = ((int) kbdata->kbstatus)) != 0) {
			not_our_int = 0;
			DEBUGF(klsdebug&0x04,
			   printf("klsint: kbdata->kbstatus=%x kbsc=%x\n",
					 kbdata->kbstatus, kbdata->kbsc));
			c = (int) kbdata->kbsc & 0xff;	/* read scan code */
			kbdata->kbstatus = 0;	/* inform PS/2 we got it */
			if (iid == KBD_ASCII)
				/* pass along ASCII byte */
				ttyinput(c, &cons[cons_if]);
			else if (iid = KBD_CPUSTATE) {
				int	altcpustate();
				timeout(altcpustate,c,0); /* print msgs off lvl*/
			}
#ifndef AT
			else if (iid == KBSTAT_HOT_KEY) {
				/*
				 * hotkey call to save the screens.
				 * we assume saving the vga really means saving
				 * everything. have screen_save and
				 * screen_restore deal with it.
				 */
				if (c == VGA_SAVE ) {
					c = screen_all_save();
				} else {
					c = screen_all_restore();
				}
				/* if we saved everything then notify the other
				 *  side
				 */
				if (!c) {
					unsigned old_window = get_512_window();
					unsigned vga_win = get_pc_cb(BIOSENT);
					struct vga_params *vga = 
					 	  (struct vga_params *)
						        (set_512_window(vga_win)+
								    pcif_512_fw);
	
					/* clear the opcode to tell the pc_code
					 * we're done
					 */
					vga->pc_opcode = 0;
					set_512_window(old_window);
					
				}
				c = 0;
			} 
#endif /* AT */
			else
				kbdflag = 1;		/* handle scan code */
		}
		/* finally check for the speaker */
		if (spkdata->status) {
			not_our_int = 0;
			spkdata->status = 0;	/* inform PS/2 we got it */
			spkflag = 1;
		}


		if (kbdflag) {	/* data was from keyboard */
			/*
			 * Determine source of keyboard interrupt by
			 * examining scan code in c.  If 0<c<=0x84 (valid
			 * make codes) or c=0xf0 (break code), then it was a
			 * keystroke (unless it is 0xaa which we can get when
			 * the keyboard is disconnected) Else, assume return
			 * code from keyboard command. 
			 */

			if (c == KBR_ERROR)	/* Keyboard Error = 0 */
				x = KLS_ERROR;
			else if ((c > 0x84) && (c != 0xf0) && (c != 0xaa)) {
				x = KLS_REQ;
			} else {
				x = KLS_KBD;
			}

			switch (x) {
			case KLS_KBD:
				/* XXX need to check if we run out of clists */
#ifdef DEBOUNCE
				if (c != klslastscan)
					putc(c, &kbd_data);
				klslastscan = c;
#else
				putc(c, &kbd_data);
#endif DEBOUNCE
				break;
			case KLS_REQ:
				DEBUGF(klsdebug, 
				       printf("KLSINT: kbd resp (0x%x)\n", c););
				if (klsret != KLSINVRET) {
				  printf("klsint: extra return status found \n");
#ifdef DEBUG
				  printf(" old=0x%x new=0x%x dest=0x%x cmd=0x%x call=0x%x\n"
				      ,klsret,((iid << 8) + c),klshead->qp_dest,
				         klshead->qp_cmd,klshead->qp_callback);
#endif DEBUG
				}
				klsret = (iid << 8) + c;
				break;
			case KLS_ERROR:
				if (c != lastscan)
					klserror(lastscan);
				break;
			default:
				printf("klsint: received unexpected iid = %x\n",
									 iid);
			}
			lastscan = c;	/* to "debounce" errors */
		}			/* End if(kbdflag) */
	
		set_512_window(old_window);	/* restore the window */

		/* call each of the interrupt routines if data is for them */
		if (klsret != KLSINVRET) {
			DEBUGF(klsdebug, 
			   printf("KLSINT: calling klssendint with ret=0x%x\n",
								 klsret););
			klssendint(klsret);
		}
		if (kbd_data.c_cc > 0)
			kbdint();
#if NMS > 0
		if (ms_uart.c_cc > 0)
			msrint();
#endif NMS
		if (spkflag)
			spkrint();
	
		return (not_our_int);
	}
}


/* get queue command up from earlier level */
klsstrategy(qp)
	register struct klsq *qp;
{
	register int    s = KLSSPL();

	qp->qp_next = NULL;
	qp->qp_ret = KLSINVRET;
	if (klshead == NULL) {
		klshead = qp;
		klstail = qp;
		if (!klsinreset) {
			DEBUGF(klsdebug, printf("KLSSTRATEGY: calling klsstart\n"));
			klsstart();
		}
	} else {
		klstail->qp_next = qp;
		klstail = qp;
	}
	splx(s);
}

/*
 * change in state of one of the other cpu's
 * we are notified by a pseudo keyboard code
 * and queued for execution via a timeout so that
 * we don't inhibit interrupts by printing at keyboard
 * interrupt level.
 */
static char *cpustates[16] = CPU_STATES;
altcpustate(sc)
{
	int cpu = (sc >> 4) & 0x0f;
	char *state = cpustates[sc & 0x0f];

	if (state == 0)
		state = "unknown";

	printf("cpu %d now %s\n",cpu,state);
}


/* send command to keyboard or mouse */
klsstart()
{
	register int    old_window;	/* to save current window */
#if NMS > 0
	register struct pcms_cmd *mscmd = &pcms_cmd;
#endif NMS
	DEBUGF(klsdebug, printf("In KLSSTART\n"));

	if (klshead == NULL)
		return;

	switch (klshead->qp_dest) {
	case KBCMD:
		old_window = get_512_window();	/* save current window */
		set_512_window(kb_pc_cb);	/* point to the cbcb */

		kbdata->cmd_dest = klshead->qp_dest;
		kbdata->cmd_code = klshead->qp_cmd;	/* get cmd into cbcb */

		set_512_window(old_window);	/* restore the window */

		/* make request to PC */
		if (pc_req(CB_KBREQ, 0, KBENT) < 0)
			printf("klsstart: kbd cmd request to pc failed\n");
		break;
#if NMS > 0
	case MSCMD:
		mscmd->cmd = klshead->qp_cmd;
		mscmd->param = klshead->qp_param;
		DEBUGF(klsdebug, printf("KLSSTART: mouse pc_req, cmd=0x%x, param=0x%x\n", mscmd->cmd, mscmd->param);
		);
		/* make request to PC */
		if (pc_req(CB_MSREQ, mscmd, MSENT) < 0)
			printf("klsstart: mouse request to pc failed\n");
		break;
#endif NMS
	case SPKCMD:
		break;
	default:
		printf("klsstart: unknown command destination: %x\n",
		       klshead->qp_dest);
	}

}

/* handle return status & start next command on queue */
klssendint(retstat)
	register int    retstat;
{
	register struct klsq *current = klshead;

	DEBUGF(klsdebug, printf("In KLSSENDINT\n"));
	if (current == NULL) {
		printf("klssendint: no commands on queue!\n");
		return;
	}
	current->qp_ret = retstat;
	if ((klshead = current->qp_next) != NULL)
		klsstart();
	if (current->qp_callback)
		(*current->qp_callback) (current);
	else
		panic("klssendint: no callback ");
}


/*
 * General command routine, used for queueing commands not requiring
 * special attention from the drivers 
 */
klscmd(dest,cmd,callback)
	register	char dest,cmd;
	register	int (*callback)();
{
	return(0);
}

klsdone(qp)
	register struct klsq *qp;
{
	klsfree(klsfreel, qp);
}

caddr_t
klsminit(pool, struct_size, pool_size)
	caddr_t         pool;
	int             struct_size, pool_size;
{
	int             count;
	caddr_t         current = pool;

	/*
	 * the following cryptic loop links the elements of the pool
	 * together. This method is used to allow linking pools of different
	 * sizes 
	 */
	for (count = 0; count < pool_size - 1; count++) {
		*(caddr_t *) current = (current + struct_size);
		current += struct_size;
	}
	*(caddr_t *) current = NULL;
	return (pool);
}

struct klsq    *
klsalloc(free)
	register struct klsq **free;
{
	register struct klsq *tmp = *free;

	*free = (tmp ? tmp->qp_next : NULL);
	return (tmp);
}

klserror(c)
	int             c;
{
	printf("klsint: Keyboard error code detected. Last char = %x.\n", c);
}

/*
 * Used by autoconf to wait for kls initialization to complete before
 * starting to probe controllers and devices.
 */
klswait()
{
	spkwait();
#if NMS > 0
	mswait();
#endif NMS
	kbdwait();
	while (klshead != NULL);
}
