/* * 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 #include #include #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); }