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

/*
 * Copyright University of Southern California, 1988
 */

#include <doscalls.h>
#include <dos.h>
#include <subcalls.h>
#include "pctype.h"
#include "pcparam.h"
#include "rb.h"
#include "os2ioctl.h"
#include "os2data.h"
#include "sem.h"
#include "kbd.h"
#include "thread.h"

#ifdef PROFILING
#include "profile.h"
#endif

static struct kbqueue kbqueue = { 0 };	/* defined in rb.h */
static u_int	kbd_mon_handle;	/* handle to the keyboard monitor */
u_int	kbd_tid;		/* id of the keyboard thread */
static struct kbd_mon_buffer kbd_bufi;
static struct kbd_mon_buffer kbd_bufo;
static struct kbd_mon_buffer kbd_pvt_buf;
static u_int  kbd_cmd_ack=0;	/* request to send unix an ack */
static u_int  kbd_set_led=0;	/* request to send unix an ack */
static u_int  kbd_leds = 0;
static u_int  kbd_handle = 0;
static u_int shift_mode=0;	/* current state of the shift keys 
				 * maintained to detect escape sequences
				 */
static u_char	last_cntrl;	/* the last control key sent */
static u_char	last_alt;	/* the last alt key sent */
static u_int	expect_shift_break = 0; /* flag */

static u_int	expect_quit_key = 0;	/* flag */
#define ABORT_STRING "Escape Sequence Detected\nDo you want to abort UNIX?"

/*
 * Scan code generated by the Enhanced PS/2 keyboard (when using RT
 * scan code set).
 */

#define BREAK		240
#define ALT		25
#define CTL		17
#define SCROLL_LOCK	95
#define DEL		113
#define PAUSE		98
#define PRINT_SCREEN	87
#define NUM_LOCK	118
#define ESC_SC		8

/* following are in "mode" below */
#define ALT_MODE        1		/* ALT key is down */
#define CTL_MODE        2		/* CONTROL key is down */

#define SCAN1_ESCAPE	0xe0	/* next character is special */
#define SCAN1_ESCAPE2	0xe1	/* next character is extra special */
#define SCAN1_BREAK	0x80	/* bit for scan 1 break */
#define SCAN3_BREAK	0xf0	/* value for scan 3 break */
#define NONE	0x00		/* no scan code/key */
#define KBDSTAT_NOP	0x00		/* empty */
#define	KBDSTAT_V_CHAR	0x01		/* valid character */
#define KBDSTAT_HOT_KEY	0x03		/* hot key detected */

static unsigned char far scan1_table[128] = 
{ /*
 0     1     2     3     4     5     6     7     8     9    	       */
NONE, 0x29, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,	/*  0x */
0x0a, 0x0b, 0x0c, 0x0d, NONE, 0x0e, 0x0f, 0x10, 0x11, 0x12,	/*  1x */
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x2b, 	/*  2x */
0x3a, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,	/*  3x */
0x27, 0x28, 0x2b, 0x1c, 0x2a, 0x56, 0x2c, 0x2d, 0x2e, 0x2f,	/*  4x */
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, NONE, 0x36, 0x1d, NONE,	/*  5x */
0x38, 0x39, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,	/*  6x */
NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,	/*  7x */
NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,	/*  8x */
0x45, 0x47, 0x4b, 0x4f, NONE, NONE, 0x48, 0x4c, 0x50, 0x52,	/*  9x */
0x37, 0x49, 0x4d, 0x51, 0x53, 0x4a, 0x4e, NONE, NONE, NONE,	/* 10x */
0x01, NONE, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,	/* 11x */
0x43, 0x44, 0x57, 0x58, 0x54, 0x46, NONE };			/* 12x */

static unsigned char scan3_table[128] = 
{ /*
 0     1     2     3     4     5     6     7     8     9    	       */
0x00, 0x0e, 0x16, 0x1e, 0x26, 0x25, 0x2e, 0x36, 0x3d, 0x3e,	/*  0x */
0x46, 0x45, 0x4e, 0x55, NONE, 0x66, 0x0d, 0x15, 0x1d, 0x24,	/*  1x */
0x2d, 0x2c, 0x35, 0x3c, 0x43, 0x44, 0x4d, 0x54, 0x5b, 0x5c,	/*  2x */
0x14, 0x1c, 0x1b, 0x23, 0x2b, 0x34, 0x33, 0x3b, 0x42, 0x4b,	/*  3x */
0x4c, 0x52, 0x53, 0x5a, 0x12, 0x13, 0x1a, 0x22, 0x21, 0x2a,	/*  4x */
0x32, 0x31, 0x3a, 0x41, 0x49, 0x4a, NONE, 0x59, 0x11, NONE,	/*  5x */
0x19, 0x29, 0x39, NONE, 0x58, NONE, NONE, NONE, NONE, NONE,	/*  6x */
NONE, NONE, NONE, NONE, NONE, 0x67, 0x64, NONE, NONE, 0x61,	/*  7x */
0x6e, 0x65, NONE, 0x63, 0x60, 0x6f, 0x6d, NONE, NONE, 0x6a,	/*  8x */
0x76, 0x6c, 0x6b, 0x69, NONE, 0x77, 0x75, 0x73, 0x72, 0x70,	/*  9x */
0x7e, 0x7d, 0x74, 0x7a, 0x71, 0x84, 0x7c, NONE, 0x79, NONE,	/* 10x */
0x08, NONE, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, 0x3f,	/* 11x */
0x47, 0x4f, 0x56, 0x5e, 0x57, 0x5f, 0x62 };			/* 12x */

static unsigned char far scan1_to_scan3[256];


/*
 * determine if we are using scan code set 1. If not we just call
 * kbscan to process the scan code set 3 value. 
 * if using scan code set 1 then we map the scan code to the appropriate
 * scan code set 3 value and invoke kbscan.
 */

/*
 * build the mapping table from scan 1 codes to scan 3 codes 
 */
static void scan_init()
{
	unsigned int i, j;

	for (i=0; i<128; ++i) {
		j = scan1_table[i];
		if (j != NONE && scan1_to_scan3[j] == NONE)
			scan1_to_scan3[j] = scan3_table[i];
	}
	/*
	 * handle special cases where we first have E0 then
	 * the scan code set 1 code, this allows use of all
	 * keyboard keys even when in PC compatible scan code
	 * sets.
	 */
	scan1_to_scan3[0x38+0x80] = 0x39;	/* key 62 */
	scan1_to_scan3[0x1d+0x80] = 0x58;	/* key 64 */
	scan1_to_scan3[0x52+0x80] = 0x67;	/* key 75 */
	scan1_to_scan3[0x53+0x80] = 0x64;	/* key 76 */
	scan1_to_scan3[0x4b+0x80] = 0x61;	/* key 79 */
	scan1_to_scan3[0x47+0x80] = 0x6e;	/* key 80 */
	scan1_to_scan3[0x4f+0x80] = 0x65;	/* key 81 */
	scan1_to_scan3[0x48+0x80] = 0x63;	/* key 83 */
	scan1_to_scan3[0x50+0x80] = 0x60;	/* key 84 */
	scan1_to_scan3[0x49+0x80] = 0x6f;	/* key 85 */
	scan1_to_scan3[0x51+0x80] = 0x6d;	/* key 86 */
	scan1_to_scan3[0x4d+0x80] = 0x6a;	/* key 89 */
	scan1_to_scan3[0x35+0x80] = 0x77;	/* key 95 */
	scan1_to_scan3[0x1c+0x80] = 0x79;	/* key 108 */
	scan1_to_scan3[0x37+0x80] = 0x57;	/* key 124 */
	scan1_to_scan3[0x45+0x80] = 0x62;	/* key 126 */
	scan1_to_scan3[0x46+0x80] = 0x62;	/* key 126 */
}
/* functions to operate on the keyboard queue
 * NOTE: Although these functions do not appear to be protected by critical
 * 	 sections, they are, in reality, called only from within critical
 *	 sections. IT IS ESSENTIAL THAT THIS BE FOLLOWED.
 */

static void
push_kbdq(sc)
u_char	sc;

{	register struct kbqueue *kbq = &kbqueue;
	register int i;

	i = kbq->tail;
	kbq->buff[i] = (short) sc;	/* buff is a short */
	i = (i + 1) % MAXKBBUFF;
	if ( i != kbq->head )
		kbq->tail = i;
	else 
		printf("push_kbdq: kbqueue overflow\n");
}

static int
pop_kbdq(sc)
u_char *sc;	/* returned scan code if present */

{	register struct kbqueue *kbq = &kbqueue;
	register int i;

	if ( (i = kbq->head) != kbq->tail ) {
		*sc = (u_char) (kbq->buff[i] & 0xff);
		i = (i + 1) % MAXKBBUFF;
		kbq->head = i;
		return 1;		/* valid scan code available */
	}
	return 0;	/* queue empty */
}

static void
reset_kbdq()

{	register struct kbqueue *kbq = &kbqueue;

	kbq->head = 0;
	kbq->tail = 0;
}


/* Predicate to determine if we can send a character to UNIX.
 */

static int
can_send()

{
	if ( kbdata.kbstatus ) {
		return 0;
	}
	return 1;
}


/* send a character to unix.
 * the function DOES NOT check to see if the last character was sent.
 * ESSENTIAL: This function must be non blocking - we may lose keystrokes
 * or suffer in response time otherwise.
 * THIS MUST BE PROTECTED BY A CRITICAL SECTION.
 */

static void
send_char(code,status)
register u_char	code;
register u_char	status;

{
	kbdata.kbsc = code;
	kbdata.kbstatus = status;
	if( kbdata.fwd_int ) 
		rompint3(FAKEPOLL(KBIRQ)); 
}

/* macro to send/enqueue a keyboard character correctly */

#define SEND_CHAR(c)	qsend_char(c)

/* this function checks if unix has processed the previous key before
 * attempting to send the next one on.
 */

static int
qsend_char(c)
register u_char c;

{	u_char sc;

	ENTER_CS();
	if ( can_send() ) {
		if ( pop_kbdq(&sc) ) {
			push_kbdq(c);
			send_char(sc,KBDSTAT_V_CHAR);
		}
		else {
			send_char(c,KBDSTAT_V_CHAR);
		}
	}
	else 
		push_kbdq(c);
	EXIT_CS();
}

/* function to send the next in the queue if possible */

static int
send_next_char()

{ 	u_char sc;

	ENTER_CS();
	if ( can_send() ) {
		if ( pop_kbdq(&sc) ) {
			send_char(sc,KBDSTAT_V_CHAR);
		}
	}
	EXIT_CS();
}

/* function to request for a hot key status to be issued to unix.
 * This function is provided for the vga driver only. It is time
 * critical and operates from within a critical section.
 */

int
send_hot_key(type,intr)
	int	type;
	int	intr;
{
	short 	i = 0x7fff;
	int	rc = 0;


	ENTER_CS();
	
	while ( (!can_send()) && --i ) ;
	/* 
	 * if "i" timed out, unix took to long reading the last key
	 * we just eat it so we can get the hotkey through. (this
	 * prevents catastrophic hangs because unix is south..)
	 */
	kbdata.kbsc = type;
	kbdata.kbstatus = KBDSTAT_HOT_KEY;

	/*
	 * don't try to send a hot key if unix can't deal with it.
	 * (e.i. unix is in the debugger).
	 */
	if ((intr) && (kbdata.fwd_int)) {
		/* must make sure that it interrupts unix */
			rc =send_priority_kbd_intr();
	}

	EXIT_CS();

	return rc;
}
	
static void far
kbd_intr()
{
	u_short	sc;
	u_short	temp;
	static	u_short lastscan;
	u_int	count;
	int	rc,flag;
	int	keys_read = 0;
	register struct kbd_mon_buffer *kdp = &kbd_pvt_buf;

	SET_THREAD_PRIORITY(PRI_KEY_THREAD);
	for(;;) {
	 count = MON_BUF_SIZE;
	 if ( rc = DOSMONREAD((char far *)&kbd_bufi,0,(char far *)&kbd_pvt_buf,
				     (u_int far *)&count) ) {
		if ( rc != 383 ) {
			printf("kbd_scan: DOSMONREAD: %d\n",rc);
			continue;
		}
		continue;
	 }

	 flag = kdp->mon_flag;
	 sc = ((flag >> 8) & 0xff);
	 if ( (flag & MON_FLUSH) == MON_FLUSH ) {
		/* a flush packet - forward it */

		rc = DOSMONWRITE((char far *)&kbd_bufo,
			      (char far *)&kbd_pvt_buf,count);
		continue;
	 }
	 /*
	  * when using scan code set 1 we use the scan1_to_scan3
	  * translation table, also taking care of the difference
	  * in make/break indication.
	  * an added complication is the addition of a flag (SCAN1_ESCAPE)
	  * that indicates an extended scan code.
	  */
         if (lastscan == SCAN1_ESCAPE2) {
                if ((sc&~SCAN1_BREAK) == 0x1d)
                        continue;
                lastscan = SCAN1_ESCAPE;        /* lets pretend */
         }
         if (lastscan != SCAN1_ESCAPE) {
                temp = scan1_to_scan3[sc&~SCAN1_BREAK];
                if (temp) {
                        if (sc&SCAN1_BREAK)
                                kbscan(SCAN3_BREAK);
                        kbscan(temp);   /* pass it along */
                }
         } else {
                if (temp = scan1_to_scan3[sc|0x80]) {
                        if (sc&SCAN1_BREAK)
                                kbscan(SCAN3_BREAK);
                        kbscan(temp);   /* pass it along */
                }
         }
         lastscan = sc;
	}
}

kbscan(sc)
        u_short         sc;
{

        int             i;
        static int      mode;
        static int      make_break;
        extern struct msdata msdata;

        /*
         * we watch the scan codes to look for the system attention sequence:
         * control+alt+scroll_lock. If we see it then we send a level zero
         * interrupt to the romp (which should kick us into the debugger).
         * we also watch for:
         * control+alt+del      set flag for return to DOS
         * control+alt+pause    reboot
         */
        i = LOBYTE(sc);         /* get the scan code */
        switch (i)
        {
        case BREAK:
                make_break = 1;
                break;
        case ALT:
                if (make_break)
                {
                        mode &= ~ALT_MODE;
                        make_break = 0;
                } else
                        mode |= ALT_MODE;
                break;
        case CTL:
                if (make_break)
                {
                        mode &= ~CTL_MODE;
                        make_break = 0;
                } else
                        mode |= CTL_MODE;
                break;
        case DEL: /* XXX make this 'C' */
                if (!make_break && mode == (CTL_MODE | ALT_MODE))
                        exit(0);
#ifdef FAKE_MOUSE
                if (fake_mouse)
                        if (kbd_mouse(mode,make_break,sc))
                                if (make_break)
                                        sc = NOP_SC;            /* nop it */
                                else
                                        return;
#endif
                make_break = 0;
                break;
        case PAUSE:
                if (!make_break && mode == (CTL_MODE | ALT_MODE))
                	exit(0); /* flag for reboot... */
                make_break = 0;
                break;
#ifdef DEBUG_TRACE
        case NUM_LOCK:
                if (!make_break && mode == (CTL_MODE | ALT_MODE))
                        trace_print();
                make_break = 0;
                break;
#endif
#ifdef FAKE_MOUSE
        case PRINT_SCREEN:
                if (!make_break && mode == (CTL_MODE | ALT_MODE))
                {
                        fake_mouse = !fake_mouse;
                        return; /* ignore the character */
                }
#endif
        case SCROLL_LOCK:
                if (!make_break && mode == (CTL_MODE | ALT_MODE))
                {
                        rompint0();
                        return; /* ignore the character */
                }
                /* fall thru to default case */
        default:
#ifdef FAKE_MOUSE
                if (fake_mouse)
                        if (kbd_mouse(mode,make_break,sc))
                                if (make_break)
                                        sc = NOP_SC;            /* nop it */
                                else
                                        return;
#endif FAKE_MOUSE
                make_break = 0;
        }
        SEND_CHAR(sc&0xff);
	kbd_set_leds();
}

/*
 * start the keyboard thread
 */
int
start_keyboard()

{
#ifdef PROFILING
	u_long	pk_last_time;

	pt_kbdrd_cnt++;
	pk_last_time = elapsed_time();
#endif
	char	*stack;
	char	far *thstk;
	int	rc;
	char	*malloc();

	if ((stack = malloc(512)) == 0) {
		printf("No memory for keyboard thread\n");
		exit(0);
	}
	stack += 512;
	thstk = (char far *)stack;
	if (rc = DOSCREATETHREAD(kbd_intr,(u_int far *)&kbd_tid,thstk)) {
		printf("Unable to create keyboard thread: %d\n",rc);
		exit(0);
	}
}

/* Initialize the keyboard.
 * We set up a kbd monitor to trap the keystrokes directly from the keyboard
 * driver.
 */

int
kbd_init()
{	
	int	rc;
	char	*malloc();
	u_int	dummy;


	if ( rc = DOSMONOPEN((char far *) KBD_DEVICE,
			     (u_int far *)&kbd_mon_handle) ) {
		printf("kbd_init: DOSMONOPEN returned %d\n",rc);
		return -1;
	}

	kbd_bufi.mon_flag = sizeof(struct kbd_mon_buffer);
	kbd_bufo.mon_flag = sizeof(struct kbd_mon_buffer);

	/* register the buffers */
	if ( rc = DOSMONREG(kbd_mon_handle,(char far *)&kbd_bufi,
					   (char far *)&kbd_bufo,0,
					   (unsigned) -1) ){
		printf("kbd_init: DOSMONREG returned %d\n",rc);
		return -1;
	}

	if ( rc = DOSOPEN((char far *)KBD_DEVICE,(u_int far *)&kbd_handle,
		(u_int far *)&dummy,(long) 0, 0, 1, 0x2042,(long) 0) ) {
		printf("kbd_init: DOSOPEN returned %d\n",rc);
		return -1;
	}

	/* reset the keyboard queue */
	reset_kbdq();
	kbdata.kbstatus = 0;
	kbd_cmd_ack = 0;
	kbd_set_led = 0;
	kbd_leds = 0;
	shift_mode = 0;
	scan_init();
	kbd_set_leds();

	return 1;
}


/* Function to reopen the keyboard.
 * It remembers the state of the shift keys after reopening. The keyboard
 * queue is flushed.
 */

kbd_reopen()

{
	int old_shift_mode = shift_mode;

	/* re-initialize the keyboard monitor */
	if ( kbd_init() < 0 ) {
		fatal("kbd_reopen: Unable to reopen keyboard monitor\n",-1);
	}
	/* restore the shift status */
	shift_mode = old_shift_mode;

}

/* function to close the keyboard monitor.
 */

kbd_close()

{
	DOSMONCLOSE(kbd_mon_handle);
	kbd_mon_handle = 0;
}

kbd_set_leds()
{
	struct kbd_shift_state buf;

	DOSDEVIOCTL(&buf,&buf,KBD_GET_STATE,KBDIOCTL,kbd_handle);
	buf.shift_state = KBD_PUT_LIGHTS(buf.shift_state,kbd_leds);
	DOSDEVIOCTL(&buf,&buf,KBD_SET_STATE,KBDIOCTL,kbd_handle);
}

kbdcmd()
{
	/*
	 * Currently we do nothing.
	 * A return code must be faked in order for UNIX to continue.
	 *
	 * The return code must satisfy the following requirement.
	 *	rc > 0x84 && rc != 0xf0 && rc != aa.
	 */

#ifdef KBD_DEBUG
	printf("kbdcmd: cmd = %x dest = %x\n",kbdata.cmd_code,
	 					kbdata.cmd_dest);
#endif

#ifdef PROFILING
	pt_kbdcmd_cnt++;
#endif
	/* flag the thread to send the ack
	 * The setup with unix guarantees that we cannot get another
	 * command unless the current one is cleared. So we do not have
	 * to wait until kbd_cmd_ack == 0. It HAS to be 0. Just be be
	 * paranoic, we increment the kbd_cmd_ack counter.
	 */
	if (kbd_set_led) {
		kbd_leds = kbdata.cmd_code;
		kbd_set_leds();
		kbd_set_led = 0;
	}
	if (kbdata.cmd_code == KBD_SET_LED) {
		kbd_set_led = 1;
	}
	ENTER_CS();
	kbd_cmd_ack++;
	EXIT_CS();
}

send_kbd_ack()
{
	int	rc = 0;
	if (kbd_cmd_ack) {
		DOSSLEEP(KBD_SLEEP);
		SEND_CHAR(0xfa);
		ENTER_CS();
		kbd_cmd_ack--;
		EXIT_CS();
		rc = 1;
	}
	send_next_char();
	return(rc);
}

/*
 * This function drives the PS/2 speaker.
 * Note that the PS/2 speaker has no volume control so full UNIX
 * support cannot be provided. Instead, a non-zero volume is interpreted
 * as on and a zero as off.
 */

void far
speaker(unixaddr)
u_long	unixaddr;
{
	struct pcspk_cmd blk;
	char far       *pcaddr;
	register u_int	freq, dur;

	pcaddr = (char far *) &blk;
	movein(unixaddr, pcaddr, sizeof(struct pcspk_cmd));

	blk.duration = exchw(blk.duration);
	dur = (u_short)(((u_long)blk.duration * 10000L) / 182);
	if (blk.vol) {
		freq = ((u_int)blk.freqhigh << 8) | (u_int) blk.freqlow;
		/*
		 * convert ATR type frequency back into hertz
		 * (sigh, so OS/2 can convert it back). this will
		 * be the 3rd time the frequency has been mangled
		 * with (1st time user converted hertz to RT format,
		 * 2nd time unix converted from RT format to PC format)
		 */
		if (freq > 32162) {
			/*
			 * OS/2's min frequency is 37 hertz.
			 * frequency in hertz is 1190000/freq.
			 * 1190000/37 = 32162.
			 */
			freq = 37;
		} else if (freq <= 36) {
			/*
			 * OS/2's max frequency is 32767.
			 * 11900000/32767 = 36.
			 */
			freq = 32767;
		} else {
			freq = (u_short)((u_long)1190000 / (u_short)freq);
		}
		DOSBEEP(freq,dur);
	} else {
		/* sleep the duration off */
		DOSSLEEP((u_long) dur);
	}
	spkdata.status = 1;
	rompint3(FAKEPOLL(SPIRQ));
}
