OpenSolaris_b135/uts/i86pc/boot/boot_keyboard.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * Miniature keyboard driver for bootstrap.  This allows keyboard
 * support to continue after we take over interrupts and disable
 * BIOS keyboard support.
 */

#include <sys/types.h>
#include <sys/archsystm.h>
#include <sys/boot_console.h>
#include "boot_keyboard_table.h"

#if defined(_BOOT)
#include "dboot/dboot_asm.h"
#include "dboot/dboot_xboot.h"
#endif /* _BOOT */

/*
 * Definitions for BIOS keyboard state.  We use BIOS's variable to store
 * state, ensuring that we stay in sync with it.
 */
#define	BIOS_KB_FLAG		0x417
#define	BIOS_RIGHT_SHIFT	0x01
#define	BIOS_LEFT_SHIFT		0x02
#define	BIOS_EITHER_SHIFT	(BIOS_LEFT_SHIFT | BIOS_RIGHT_SHIFT)
#define	BIOS_CTL_SHIFT		0x04
#define	BIOS_ALT_SHIFT		0x08
#define	BIOS_SCROLL_STATE	0x10
#define	BIOS_NUM_STATE		0x20
#define	BIOS_CAPS_STATE		0x40
#define	BIOS_INS_STATE		0x80

#define	BIOS_KB_FLAG_1		0x418
#define	BIOS_SYS_SHIFT		0x04
#define	BIOS_HOLD_STATE		0x08
#define	BIOS_SCROLL_SHIFT	0x10
#define	BIOS_NUM_SHIFT		0x20
#define	BIOS_CAPS_SHIFT		0x40
#define	BIOS_INS_SHIFT		0x80

#if defined(__xpv) && defined(_BOOT)

/*
 * Device memory addresses
 *
 * In dboot under the hypervisor we don't have any memory mappings
 * for the first meg of low memory so we can't access devices there.
 * Intead we've mapped the device memory that we need to access into
 * a local variable within dboot so we can access the device memory
 * there.
 */
extern unsigned short *kb_status;
#define	kb_flag		((unsigned char *)&kb_status[BIOS_KB_FLAG])
#define	kb_flag_1	((unsigned char *)&kb_status[BIOS_KB_FLAG_1])

#else /* __xpv && _BOOT */

/* Device memory addresses */
#define	kb_flag		((unsigned char *)BIOS_KB_FLAG)
#define	kb_flag_1	((unsigned char *)BIOS_KB_FLAG_1)

#endif /* __xpv && _BOOT */

/*
 * Keyboard controller registers
 */
#define	I8042_DATA		0x60
#define	I8042_STAT		0x64
#define	I8042_CMD		0x64

/*
 * Keyboard controller status register bits
 */
#define	I8042_STAT_OUTBF	0x01
#define	I8042_STAT_INBF		0x02
#define	I8042_STAT_AUXBF	0x20

/*
 * Keyboard controller commands
 */
#define	I8042_RCB		0x20
#define	I8042_WCB		0x60

/*
 * Keyboard commands
 */
#define	KB_SET_LED		0xED	/* LED byte follows... */
#define	KB_LED_SCROLL_LOCK	0x01	/* Bits for LED byte */
#define	KB_LED_NUM_LOCK		0x02
#define	KB_LED_CAPS_LOCK	0x04

#ifndef ASSERT
#define	ASSERT(x)
#endif

#define	peek8(p)	(*(p))
#define	poke8(p, val)	(*(p) = (val))

static struct {
	boolean_t	initialized;
	enum { KB_LED_IDLE, KB_LED_COMMAND_SENT, KB_LED_VALUE_SENT }
			led_state;
	int		led_commanded;
	/*
	 * Possible values:
	 *
	 * -1		Nothing pending
	 * 0x000-0x0ff	Pending byte
	 * 0x100-0x1ff	Needs leading zero, then low byte next.
	 *
	 * Others undefined.
	 */
	int		pending;
} kb = {
	B_FALSE,	/* initialized? */
	KB_LED_IDLE,	/* LED command state */
	-1,		/* commanded LEDs - force refresh */
	-1,		/* pending */
};

static int kb_translate(unsigned char code);
static void kb_send(unsigned char cmd);
static void kb_update_leds(void);
static uchar_t kb_calculate_leds(void);

int
kb_getchar(void)
{
	int ret;

	while (!kb_ischar())
		/* LOOP */;

	/*
	 * kb_ischar() doesn't succeed without leaving kb.pending
	 * set.
	 */
	ASSERT(kb.pending >= 0);

	if (kb.pending & 0x100) {
		ret = 0;
		kb.pending &= 0xff;
	} else {
		ret = kb.pending;
		kb.pending = -1;
	}

	return (ret);
}

int
kb_ischar(void)
{
	unsigned char buffer_stat;
	unsigned char code;
	unsigned char leds;

	if (!kb.initialized) {
		kb_init();
		kb.initialized = B_TRUE;
	}

	if (kb.pending >= 0)
		return (1);

	for (;;) {
		buffer_stat = inb(I8042_STAT);
		if (buffer_stat == 0xff)
			return (0);
		buffer_stat &= (I8042_STAT_OUTBF | I8042_STAT_AUXBF);

		switch (buffer_stat) {
		case 0:
		case I8042_STAT_AUXBF:
			return (0);
		case (I8042_STAT_OUTBF | I8042_STAT_AUXBF):
			/*
			 * Discard unwanted mouse data.
			 */
			(void) inb(I8042_DATA);
			continue;
		}

		code = inb(I8042_DATA);

		switch (code) {
		/*
		 * case 0xAA:
		 *
		 * You might think that we should ignore 0xAA on the
		 * grounds that it is the BAT Complete response and will
		 * occur on keyboard detach/reattach.  Unfortunately,
		 * it is ambiguous - this is also the code for a break
		 * of the left shift key.  Since it will be harmless for
		 * us to "spuriously" process a break of Left Shift,
		 * we just let the normal code handle it.  Perhaps we
		 * should take a hint and refresh the LEDs, but I
		 * refuse to get very worried about hot-plug issues
		 * in this mini-driver.
		 */
		case 0xFA:

			switch (kb.led_state) {
			case KB_LED_IDLE:
				/*
				 * Spurious.  Oh well, ignore it.
				 */
				break;
			case KB_LED_COMMAND_SENT:
				leds = kb_calculate_leds();
				kb_send(leds);
				kb.led_commanded = leds;
				kb.led_state = KB_LED_VALUE_SENT;
				break;
			case KB_LED_VALUE_SENT:
				kb.led_state = KB_LED_IDLE;
				/*
				 * Check for changes made while we were
				 * working on the last change.
				 */
				kb_update_leds();
				break;
			}
			continue;

		case 0xE0:
		case 0xE1:
			/*
			 * These are used to distinguish the keys added on
			 * the AT-101 keyboard from the original 84 keys.
			 * We don't care, and the codes are carefully arranged
			 * so that we don't have to.
			 */
			continue;

		default:
			if (code & 0x80) {
				/* Release */
				code &= 0x7f;
				switch (keyboard_translate[code].normal) {
				case KBTYPE_SPEC_LSHIFT:
					poke8(kb_flag, peek8(kb_flag) &
					    ~BIOS_LEFT_SHIFT);
					break;
				case KBTYPE_SPEC_RSHIFT:
					poke8(kb_flag, peek8(kb_flag) &
					    ~BIOS_RIGHT_SHIFT);
					break;
				case KBTYPE_SPEC_CTRL:
					poke8(kb_flag, peek8(kb_flag) &
					    ~BIOS_CTL_SHIFT);
					break;
				case KBTYPE_SPEC_ALT:
					poke8(kb_flag, peek8(kb_flag) &
					    ~BIOS_ALT_SHIFT);
					break;
				case KBTYPE_SPEC_CAPS_LOCK:
					poke8(kb_flag_1, peek8(kb_flag_1) &
					    ~BIOS_CAPS_SHIFT);
					break;
				case KBTYPE_SPEC_NUM_LOCK:
					poke8(kb_flag_1, peek8(kb_flag_1) &
					    ~BIOS_NUM_SHIFT);
					break;
				case KBTYPE_SPEC_SCROLL_LOCK:
					poke8(kb_flag_1, peek8(kb_flag_1) &
					    ~BIOS_SCROLL_SHIFT);
					break;
				default:
					/*
					 * Ignore all other releases.
					 */
					break;
				}
			} else {
				/* Press */

				kb.pending = kb_translate(code);
				if (kb.pending >= 0) {
					return (1);
				}
			}
		}
	}
}

int
kb_translate(unsigned char code)
{
	struct keyboard_translate *k;
	unsigned short action;
	boolean_t shifted;

	k = keyboard_translate + code;

	shifted = (peek8(kb_flag) & BIOS_EITHER_SHIFT) != 0;

	switch (k->normal & 0xFF00) {
	case KBTYPE_NUMPAD:
		if (peek8(kb_flag) & BIOS_NUM_STATE)
			shifted = !shifted;
		break;
	case KBTYPE_ALPHA:
		if (peek8(kb_flag) & BIOS_CAPS_STATE)
			shifted = !shifted;
		break;
	}

	if (peek8(kb_flag) & BIOS_ALT_SHIFT)
		action = k->alted;
	else if (peek8(kb_flag) & BIOS_CTL_SHIFT)
		action = k->ctrled;
	else if (shifted)
		action = k->shifted;
	else
		action = k->normal;

	switch (action & 0xFF00) {
	case KBTYPE_NORMAL:
	case KBTYPE_ALPHA:
		return (action & 0xFF);

	case KBTYPE_NUMPAD:
	case KBTYPE_FUNC:
		return ((action & 0xFF) | 0x100);

	case KBTYPE_SPEC:
		break;

	default:
		/*
		 * Bad entry.
		 */
		ASSERT(0);
		return (-1);
	}

	/*
	 * Handle special keys, mostly shifts.
	 */
	switch (action) {
	case KBTYPE_SPEC_NOP:
	case KBTYPE_SPEC_UNDEF:
		break;

	case KBTYPE_SPEC_LSHIFT:
		poke8(kb_flag, peek8(kb_flag) | BIOS_LEFT_SHIFT);
		break;

	case KBTYPE_SPEC_RSHIFT:
		poke8(kb_flag, peek8(kb_flag) | BIOS_RIGHT_SHIFT);
		break;

	case KBTYPE_SPEC_CTRL:
		poke8(kb_flag, peek8(kb_flag) | BIOS_CTL_SHIFT);
		break;

	case KBTYPE_SPEC_ALT:
		poke8(kb_flag, peek8(kb_flag) | BIOS_ALT_SHIFT);
		break;

	case KBTYPE_SPEC_CAPS_LOCK:
		if (!(peek8(kb_flag_1) & BIOS_CAPS_SHIFT)) {
			poke8(kb_flag_1, peek8(kb_flag_1) | BIOS_CAPS_SHIFT);
			poke8(kb_flag, peek8(kb_flag) ^ BIOS_CAPS_STATE);
		}
		break;

	case KBTYPE_SPEC_NUM_LOCK:
		if (!(peek8(kb_flag_1) & BIOS_NUM_SHIFT)) {
			poke8(kb_flag_1, peek8(kb_flag_1) | BIOS_NUM_SHIFT);
			poke8(kb_flag, peek8(kb_flag) ^ BIOS_NUM_STATE);
		}
		break;

	case KBTYPE_SPEC_SCROLL_LOCK:
		if (!(peek8(kb_flag_1) & BIOS_SCROLL_SHIFT)) {
			poke8(kb_flag_1, peek8(kb_flag_1) | BIOS_SCROLL_SHIFT);
			poke8(kb_flag, peek8(kb_flag) ^ BIOS_SCROLL_STATE);
		}
		break;

	case KBTYPE_SPEC_MAYBE_REBOOT:
#if 0	/* Solaris doesn't reboot via ctrl-alt-del */
		if ((peek8(kb_flag) & (BIOS_CTL_SHIFT|BIOS_ALT_SHIFT)) ==
		    (BIOS_CTL_SHIFT|BIOS_ALT_SHIFT)) {
			reset();
			/* NOTREACHED */
		}
#endif
		break;

	default:
		/*
		 * Bad entry
		 */
		ASSERT(0);
		break;
	}

	/*
	 * Consider updating the LEDs.  This does nothing if nothing
	 * needs to be done.
	 */
	kb_update_leds();

	return (-1);
}

void
kb_send(unsigned char cmd)
{
	int retries;

	for (retries = 0;
	    (inb(I8042_STAT) & I8042_STAT_INBF) != 0 && retries < 100000;
	    retries++)
		/* LOOP */;
	outb(I8042_DATA, cmd);
}

void
kb_update_leds(void)
{
	if (kb.led_state != KB_LED_IDLE) {
		/*
		 * The state machine will take care of any additional
		 * changes that are necessary.
		 */
		return;
	}

	if (kb_calculate_leds() == kb.led_commanded) {
		kb.led_state = KB_LED_IDLE;
	} else {
		kb_send(KB_SET_LED);
		kb.led_state = KB_LED_COMMAND_SENT;
	}
}

#define	MIMR_PORT	0x21	/* Mask register for master PIC */
#define	MIMR_KB		2	/* Keyboard mask bit in master PIC */

void
kb_init(void)
{
	/*
	 * Resist the urge to muck with the keyboard/mouse.  Just assume
	 * that the bios, grub, and any optional hypervisor have left
	 * the keyboard in a sane and usable state.  Messing with it now
	 * could result it making it unusuable, which would break early
	 * kmdb debugging support.  Note that we don't actually need to
	 * disable interrupts for the keyboard/mouse since we're already
	 * in protected mode and we're not compeating with the bios for
	 * keyboard access.  Also, we don't need to disable the mouse
	 * port since our polled input routine will just drop any mouse
	 * data that it recieves.
	 */
	kb_update_leds();
}

unsigned char
kb_calculate_leds(void)
{
	int res;

	res = 0;

	if (peek8(kb_flag) & BIOS_CAPS_STATE)
		res |= KB_LED_CAPS_LOCK;

	if (peek8(kb_flag) & BIOS_NUM_STATE)
		res |= KB_LED_NUM_LOCK;

	if (peek8(kb_flag) & BIOS_SCROLL_STATE)
		res |= KB_LED_SCROLL_LOCK;

	return ((char)res);
}