/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Generic keyboard support: translation
 *
 * This module is project private.  Please see PSARC/1998/176 and
 * PSARC/1998/026 for references to the kbtrans module.
 *
 * It is believed that it is safe to call these functions within debugger mode
 * except kbtrans_dprintf.  Debugger mode is a single threaded mode where most
 * kernel services are not available, including memory allocation.  Debugger
 * mode is for kmdb and OBP debugging, where the debugger calls back into the
 * kernel to obtain console input.
 *
 * Please be _very_ careful about what external functions you call.
 */

#define	KEYMAP_SIZE_VARIABLE

#include <sys/types.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/kbd.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/kbio.h>
#include <sys/vuid_event.h>
#include <sys/consdev.h>
#include <sys/kbtrans.h>
#include <sys/errno.h>
#include <sys/promif.h>
#include <sys/varargs.h>
#include "kbtrans_lower.h"

/*
 * Internal Function Prototypes
 */
static boolean_t	kbtrans_do_compose(struct kbtrans_lower *, ushort_t,
			    ushort_t, ushort_t *);
static void		kbtrans_translate(struct kbtrans_lower *,
				struct keyboard_callback *, kbtrans_key_t,
				enum keystate);
/*
 * kbtrans_processkey:
 *
 * 	lower	- state information used by the calling driver
 *		  this parameter is passed back to the callback routines.
 * 	key	- scancode
 * 	state	- KEY_PRESSED / KEY_RELEASED
 *
 * This routine checks to see if there is a raw callback, and calls it
 * if it exists.  If there is no raw callback, the key is translated.
 * The raw callback allows the driver that called the translation module
 * to be passed untranslated scancodes.
 */
void
kbtrans_processkey(struct kbtrans_lower *lower,
	struct keyboard_callback	*cb,
	kbtrans_key_t 			key,
	enum keystate 			state)
{
	DPRINTF(PRINT_L0, PRINT_MASK_ALL, (lower, "kbtrans_processkey: "
		"newstate=%d key=%d", state, key));

	/*
	 * If there is a raw routine, then call it and return.
	 */
	if (cb->kc_keypressed_raw != NULL) {

		if (state == KEY_PRESSED) {

			cb->kc_keypressed_raw(lower->kbtrans_upper, key);
		} else {

			cb->kc_keyreleased_raw(lower->kbtrans_upper, key);
		}

		return;
	}

	/*
	 * translate the scancode into a key.
	 */
	kbtrans_translate(lower, cb, key, state);
}


/*
 * kbtrans_translate:
 *
 * 	lower	- state information used by the calling driver
 *		  this parameter is passed back to the callback routines.
 * 	key		- scan code
 * 	state	- KEY_PRESSED / KEY_RELEASED
 *
 * Called to process key events if we are in TR_ASCII or TR_EVENT
 * (sunview) mode.  This routine will call the appropriate translation_callback
 * for the character when it is done translating it.
 */
static void
kbtrans_translate(struct kbtrans_lower	*lower,
	struct keyboard_callback	*cb,
	kbtrans_key_t 			key,
	enum keystate 			newstate)
{
	unsigned		shiftmask;
	register ushort_t	entry;
	register ushort_t	entrytype;
	ushort_t		result_iso;
	unsigned short		*ke;
	int			i;
	boolean_t		good_compose;

	DPRINTF(PRINT_L0, PRINT_MASK_ALL, (lower, "KEY TRANSLATE "
		"newstate=0x%x key=0x%x\n", newstate, key));

	if (lower->kbtrans_keyboard == NULL) {
		/*
		 * Nobody has told us about this keyboard yet.
		 */
		return;
	}

	/*
	 * Get the current state of the shiftmask
	 */
	shiftmask = lower->kbtrans_shiftmask;

	/*
	 * If the key has been released, then or in the UPMASK flag.
	 */
	if (newstate == KEY_RELEASED)
		shiftmask |= UPMASK;

	/*
	 * Based on the shiftmask, lookup the keymap entry that we should
	 * be using for this scancode.
	 */
	ke = kbtrans_find_entry(lower, shiftmask, key);

	if (ke == NULL) {
		/*
		 * This is a gross error.  Cancel the repeat key and exit,
		 * we can not translate this scancode.
		 */
		cb->kc_cancel_repeat(lower->kbtrans_upper);

		return;
	}

	/*
	 * Get the key for this scancode.
	 */
	entry = *ke;

	if (entry == NONL) {
		/*
		 * NONL appears only in the Num Lock table, and indicates that
		 * this key is not affected by Num Lock.  This means we should
		 * ask for the table we would have gotten had Num Lock not been
		 * down, and translate using that table.
		 */
		ke = kbtrans_find_entry(lower, shiftmask & ~NUMLOCKMASK,
			key);

		if (ke == NULL) {
			/*
			 * This is a gross error.  Cancel the repeat key and
			 * exit, we can not translate this scancode.
			 */
			cb->kc_cancel_repeat(lower->kbtrans_upper);

			return;
		}

		/*
		 * Get the new key for this scancode.
		 */
		entry = *ke;
	}

	/*
	 * The entrytype indicates what category of key we are processing.
	 * Categories include shift keys, function keys, and numeric keypad
	 * keys.
	 */
	entrytype = (ushort_t)(entry & 0xFF00);

	if (entrytype == SHIFTKEYS) {
		/*
		 * Handle the state of toggle shifts specially.
		 * Ups should be ignored, and downs should be mapped to ups if
		 * that shift is currently on.
		 */
		if ((1 << (entry & 0x0F)) &
		    lower->kbtrans_keyboard->k_toggleshifts) {
			if ((1 << (entry & 0x0F)) &
				lower->kbtrans_togglemask) {
				newstate = KEY_RELEASED; /* toggling off */
			} else {
				newstate = KEY_PRESSED;	/* toggling on */
			}
		}
	} else {
		/*
		 * Handle Compose and floating accent key sequences
		 */
		switch (lower->kbtrans_state) {
		case COMPOSE1:
			if (newstate == KEY_RELEASED)

				return;

			if (entry < ASCII_SET_SIZE) {
				if (lower->kbtrans_compose_map[entry] >= 0) {
					lower->kbtrans_compose_key = entry;
					lower->kbtrans_state = COMPOSE2;

					return;
				}
			}
			lower->kbtrans_state = NORMAL;
			lower->kbtrans_led_state &= ~LED_COMPOSE;

			cb->kc_setled(lower->kbtrans_upper);

			return;

		case COMPOSE2:
			if (newstate == KEY_RELEASED)
				return;

			/* next state is "normal" */
			lower->kbtrans_state = NORMAL;
			lower->kbtrans_led_state &= ~LED_COMPOSE;

			cb->kc_setled(lower->kbtrans_upper);

			good_compose = kbtrans_do_compose(lower,
				lower->kbtrans_compose_key, entry,
				&result_iso);
			if (good_compose) {
				if (lower->kbtrans_compat)
					result_iso += ISO_FIRST;
				else
					result_iso += EUC_FIRST;
				cb->kc_keypressed(lower->kbtrans_upper,
				    entrytype, key, result_iso);
			}
			return;

		case FLTACCENT:
			if (newstate == KEY_RELEASED)

				return;

			/* next state is "normal" */
			lower->kbtrans_state = NORMAL;
			for (i = 0;
			    (lower->kbtrans_fltaccent_table[i].fa_entry
				!= lower->kbtrans_fltaccent_entry) ||
			    (lower->kbtrans_fltaccent_table[i].ascii != entry);
			    i++) {
				if (lower->kbtrans_fltaccent_table[i].fa_entry
				    == 0) {
					/* Invalid second key: ignore key */

					return;
				}
			}

			cb->kc_keypressed(lower->kbtrans_upper, entrytype,
					key, (lower->kbtrans_compat ?
						ISO_FIRST : EUC_FIRST) +
					lower->kbtrans_fltaccent_table[i].iso);

			return;
		}
	}

	/*
	 * If the key is going down, and it's not one of the keys that doesn't
	 * auto-repeat, set up the auto-repeat timeout.
	 *
	 * The keys that don't auto-repeat are the Compose key,
	 * the shift keys, the "bucky bit" keys, the "floating accent" keys,
	 * and the function keys when in TR_EVENT mode.
	 */
	if (newstate == KEY_PRESSED && entrytype != SHIFTKEYS &&
	    entrytype != BUCKYBITS && entrytype != FUNNY &&
	    entrytype != FA_CLASS) {

		if (lower->kbtrans_repeatkey != key) {
			cb->kc_cancel_repeat(lower->kbtrans_upper);
			cb->kc_setup_repeat(lower->kbtrans_upper, entrytype,
				key);
		}
		/* key going up */
	} else if (key == lower->kbtrans_repeatkey) {

		cb->kc_cancel_repeat(lower->kbtrans_upper);
	}

	if (newstate == KEY_RELEASED) {
		cb->kc_keyreleased(lower->kbtrans_upper, key);
	}

	/*
	 * We assume here that keys other than shift keys and bucky keys have
	 * entries in the "up" table that cause nothing to be done, and thus we
	 * don't have to check for newstate == KEY_RELEASED.
	 */
	switch (entrytype) {

	case 0x0:		/* regular key */
		cb->kc_keypressed(lower->kbtrans_upper, entrytype, key,
			entry | lower->kbtrans_buckybits);
		break;

	case SHIFTKEYS: {
		uint_t shiftbit = 1 << (entry & 0x0F);

		/* Modify toggle state (see toggle processing above) */
		if (shiftbit & lower->kbtrans_keyboard->k_toggleshifts) {
			if (newstate == KEY_RELEASED) {
				if (shiftbit == CAPSMASK) {
					lower->kbtrans_led_state &=
						~LED_CAPS_LOCK;

					cb->kc_setled(lower->kbtrans_upper);

				} else if (shiftbit == NUMLOCKMASK) {
					lower->kbtrans_led_state &=
						    ~LED_NUM_LOCK;

					cb->kc_setled(lower->kbtrans_upper);
				}
				lower->kbtrans_togglemask &= ~shiftbit;
			} else {
				if (shiftbit == CAPSMASK) {
					lower->kbtrans_led_state |=
						LED_CAPS_LOCK;

					cb->kc_setled(lower->kbtrans_upper);
				} else if (shiftbit == NUMLOCKMASK) {
					lower->kbtrans_led_state |=
						LED_NUM_LOCK;

					cb->kc_setled(lower->kbtrans_upper);
				}
				lower->kbtrans_togglemask |= shiftbit;
			}
		}

		if (newstate == KEY_RELEASED)
			lower->kbtrans_shiftmask &= ~shiftbit;
		else
			lower->kbtrans_shiftmask |= shiftbit;

		if (newstate == KEY_PRESSED) {
			cb->kc_keypressed(lower->kbtrans_upper, entrytype, key,
				entry);
		}

		break;
		}

	case BUCKYBITS:
		lower->kbtrans_buckybits ^= 1 << (7 + (entry & 0x0F));

		if (newstate == KEY_PRESSED) {
			cb->kc_keypressed(lower->kbtrans_upper, entrytype, key,
				entry);
		}

		break;

	case FUNNY:
		switch (entry) {
		case NOP:
			break;

		case IDLE:
			/* Fall thru into RESET code */
			/* FALLTHRU */
		case RESET:
		case ERROR:
			lower->kbtrans_shiftmask &=
				lower->kbtrans_keyboard->k_idleshifts;

			lower->kbtrans_shiftmask |=
					lower->kbtrans_togglemask;

			lower->kbtrans_buckybits &=
				lower->kbtrans_keyboard->k_idlebuckys;

			cb->kc_cancel_repeat(lower->kbtrans_upper);

			cb->kc_keypressed(lower->kbtrans_upper, entrytype, key,
				entry);

			break;


		case COMPOSE:
			lower->kbtrans_state = COMPOSE1;
			lower->kbtrans_led_state |= LED_COMPOSE;
			cb->kc_setled(lower->kbtrans_upper);
			break;
		/*
		 * Remember when adding new entries that,
		 * if they should NOT auto-repeat,
		 * they should be put into the IF statement
		 * just above this switch block.
		 */
		default:
			/* Ignore it */
			break;
		}
		break;

	case FA_CLASS:
		if (lower->kbtrans_state == NORMAL) {
			lower->kbtrans_fltaccent_entry = entry;
			lower->kbtrans_state = FLTACCENT;
		}
		break;

	case STRING:
		cb->kc_keypressed(lower->kbtrans_upper, entrytype, key, entry);

		break;

	case FUNCKEYS:
		cb->kc_keypressed(lower->kbtrans_upper, entrytype, key, entry);

		break;

	/*
	 * Remember when adding new entries that,
	 * if they should NOT auto-repeat,
	 * they should be put into the IF statement
	 * just above this switch block.
	 */
	case PADKEYS:
		cb->kc_keypressed(lower->kbtrans_upper, entrytype, key, entry);

		break;
	}
}

/*
 * kbtrans_do_compose:
 *	Given a two key compose sequence, lookup the iso equivalent and put
 * 	the result in the result_iso_ptr.
 */
static boolean_t
kbtrans_do_compose(struct kbtrans_lower *lower,
		ushort_t	first_entry,
		ushort_t	second_entry,
		ushort_t	*result_iso_ptr)
{
	struct compose_sequence_t *ptr;
	ushort_t	tmp;

	/*
	 * Validate the second keystroke.
	 */
	if (second_entry >= ASCII_SET_SIZE)
		return (B_FALSE);

	if (lower->kbtrans_compose_map[second_entry] < 0)
		return (B_FALSE);

	/*
	 * Get them in code order, rather than press order.
	 */
	if (first_entry > second_entry) {
		tmp = first_entry;
		first_entry = second_entry;
		second_entry = tmp;
	}

	ptr = lower->kbtrans_compose_table +
		    lower->kbtrans_compose_map[first_entry];

	while (ptr->first == first_entry) {
		if (ptr->second == second_entry) {
			*result_iso_ptr = ptr->iso;

			return (B_TRUE);
		}
		ptr++;
	}
	return (B_FALSE);
}


/*
 * kbtrans_find_entry:
 * 	This routine finds the entry corresponding to the current shift
 * 	state and keycode.
 */
unsigned short *
kbtrans_find_entry(struct kbtrans_lower *lower,
	register uint_t			mask,
	kbtrans_key_t			key_station)
{
	register struct keyboard *kp;
	keymap_entry_t *km;
	struct exception_map *ex;

	kp = lower->kbtrans_keyboard;

	if (kp == NULL)
		return (NULL);

	if (key_station < 0 || key_station >= kp->k_keymap_size)
		return (NULL);

	ex = kp->k_except;
	if (ex != NULL) {
		for (; ex->exc_care != 0; ex++) {
			if ((mask & ex->exc_care) == ex->exc_mask &&
			    key_station == ex->exc_key)
				return (&ex->exc_entry);
		}
	}

	if (mask & UPMASK)
		km = kp->k_up;
	else if (mask & NUMLOCKMASK)
		km = kp->k_numlock;
	else if (mask & CTRLMASK)
		km = kp->k_control;
	else if (mask & ALTGRAPHMASK)
		km = kp->k_altgraph;
	else if (mask & SHIFTMASK)
		km = kp->k_shifted;
	else if (mask & CAPSMASK)
		km = kp->k_caps;
	else km = kp->k_normal;

	return (&km[key_station]);
}

#ifdef DEBUG
/*ARGSUSED*/
void
kbtrans_dprintf(void *un, const char *fmt, ...)
{
	char buf[256];
	va_list ap;

	va_start(ap, fmt);
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);

	cmn_err(CE_CONT, "kbtrans: %s", buf);
}
#endif