root/usr/src/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.
 */

/*
 * 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 */
static unsigned char *kb_status = ((unsigned char *)BIOS_KB_FLAG);
#define kb_flag         (&kb_status[0])
#define kb_flag_1       (&kb_status[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 */
};

#define KTAB_STRLEN 3
static char keystringtab[KTAB_STRLEN] = {'\033', '[', ' '};
static int keystring = -1;

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 */;

        if (keystring >= 0) {
                ret = keystringtab[keystring++];
                if (keystring == KTAB_STRLEN) {
                        keystring = -1;
                        kb.pending = -1;
                }
                return (ret);
        }

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

        if (kb.pending & 0x100) {
                kb.pending &= 0xff;
                switch (kb.pending) {
                case 'H':               /* Up */
                        keystringtab[2] = 'A';
                        keystring = 0;
                        return (kb_getchar());
                case 'P':               /* Down */
                        keystringtab[2] = 'B';
                        keystring = 0;
                        return (kb_getchar());
                case 'M':               /* Right */
                        keystringtab[2] = 'C';
                        keystring = 0;
                        return (kb_getchar());
                case 'K':               /* Left */
                        keystringtab[2] = 'D';
                        keystring = 0;
                        return (kb_getchar());
                default:
                        ret = 0;
                }
        } 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);
}