Minix2.0/src/kernel/keyboard.c
/* Keyboard driver for PC's and AT's.
*
* Changed by Marcus Hampel (04/02/1994)
* - Loadable keymaps
*/
#include "kernel.h"
#include <termios.h>
#include <signal.h>
#include <unistd.h>
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/keymap.h>
#include "tty.h"
#include "keymaps/us-std.src"
/* Standard and AT keyboard. (PS/2 MCA implies AT throughout.) */
#define KEYBD 0x60 /* I/O port for keyboard data */
/* AT keyboard. */
#define KB_COMMAND 0x64 /* I/O port for commands on AT */
#define KB_GATE_A20 0x02 /* bit in output port to enable A20 line */
#define KB_PULSE_OUTPUT 0xF0 /* base for commands to pulse output port */
#define KB_RESET 0x01 /* bit in output port to reset CPU */
#define KB_STATUS 0x64 /* I/O port for status on AT */
#define KB_ACK 0xFA /* keyboard ack response */
#define KB_BUSY 0x02 /* status bit set when KEYBD port ready */
#define LED_CODE 0xED /* command to keyboard to set LEDs */
#define MAX_KB_ACK_RETRIES 0x1000 /* max #times to wait for kb ack */
#define MAX_KB_BUSY_RETRIES 0x1000 /* max #times to loop while kb busy */
#define KBIT 0x80 /* bit used to ack characters to keyboard */
/* Miscellaneous. */
#define ESC_SCAN 1 /* Reboot key when panicking */
#define SLASH_SCAN 53 /* to recognize numeric slash */
#define HOME_SCAN 71 /* first key on the numeric keypad */
#define DEL_SCAN 83 /* DEL for use in CTRL-ALT-DEL reboot */
#define CONSOLE 0 /* line number for console */
#define MEMCHECK_ADR 0x472 /* address to stop memory check after reboot */
#define MEMCHECK_MAG 0x1234 /* magic number to stop memory check */
#define kb_addr() (&kb_lines[0]) /* there is only one keyboard */
#define KB_IN_BYTES 32 /* size of keyboard input buffer */
PRIVATE int alt1; /* left alt key state */
PRIVATE int alt2; /* right alt key state */
PRIVATE int capslock; /* caps lock key state */
PRIVATE int esc; /* escape scan code detected? */
PRIVATE int control; /* control key state */
PRIVATE int caps_off; /* 1 = normal position, 0 = depressed */
PRIVATE int numlock; /* number lock key state */
PRIVATE int num_off; /* 1 = normal position, 0 = depressed */
PRIVATE int slock; /* scroll lock key state */
PRIVATE int slock_off; /* 1 = normal position, 0 = depressed */
PRIVATE int shift; /* shift key state */
PRIVATE char numpad_map[] =
{'H', 'Y', 'A', 'B', 'D', 'C', 'V', 'U', 'G', 'S', 'T', '@'};
/* Keyboard structure, 1 per console. */
struct kb_s {
char *ihead; /* next free spot in input buffer */
char *itail; /* scan code to return to TTY */
int icount; /* # codes in buffer */
char ibuf[KB_IN_BYTES]; /* input buffer */
};
PRIVATE struct kb_s kb_lines[NR_CONS];
FORWARD _PROTOTYPE( int kb_ack, (void) );
FORWARD _PROTOTYPE( int kb_wait, (void) );
FORWARD _PROTOTYPE( int func_key, (int scode) );
FORWARD _PROTOTYPE( int scan_keyboard, (void) );
FORWARD _PROTOTYPE( unsigned make_break, (int scode) );
FORWARD _PROTOTYPE( void set_leds, (void) );
FORWARD _PROTOTYPE( int kbd_hw_int, (int irq) );
FORWARD _PROTOTYPE( void kb_read, (struct tty *tp) );
FORWARD _PROTOTYPE( unsigned map_key, (int scode) );
/*===========================================================================*
* map_key0 *
*===========================================================================*/
/* Map a scan code to an ASCII code ignoring modifiers. */
#define map_key0(scode) \
((unsigned) keymap[(scode) * MAP_COLS])
/*===========================================================================*
* map_key *
*===========================================================================*/
PRIVATE unsigned map_key(scode)
int scode;
{
/* Map a scan code to an ASCII code. */
int caps, column;
u16_t *keyrow;
if (scode == SLASH_SCAN && esc) return '/'; /* don't map numeric slash */
keyrow = &keymap[scode * MAP_COLS];
caps = shift;
if (numlock && HOME_SCAN <= scode && scode <= DEL_SCAN) caps = !caps;
if (capslock && (keyrow[0] & HASCAPS)) caps = !caps;
if (alt1 || alt2) {
column = 2;
if (control || alt2) column = 3; /* Ctrl + Alt1 == Alt2 */
if (caps) column = 4;
} else {
column = 0;
if (caps) column = 1;
if (control) column = 5;
}
return keyrow[column] & ~HASCAPS;
}
/*===========================================================================*
* kbd_hw_int *
*===========================================================================*/
PRIVATE int kbd_hw_int(irq)
int irq;
{
/* A keyboard interrupt has occurred. Process it. */
int code;
unsigned km;
register struct kb_s *kb;
/* Fetch the character from the keyboard hardware and acknowledge it. */
code = scan_keyboard();
/* The IBM keyboard interrupts twice per key, once when depressed, once when
* released. Filter out the latter, ignoring all but the shift-type keys.
* The shift-type keys 29, 42, 54, 56, 58, and 69 must be processed normally.
*/
if (code & 0200) {
/* A key has been released (high bit is set). */
km = map_key0(code & 0177);
if (km != CTRL && km != SHIFT && km != ALT && km != CALOCK
&& km != NLOCK && km != SLOCK && km != EXTKEY)
return 1;
}
/* Store the character in memory so the task can get at it later. */
kb = kb_addr();
if (kb->icount < KB_IN_BYTES) {
*kb->ihead++ = code;
if (kb->ihead == kb->ibuf + KB_IN_BYTES) kb->ihead = kb->ibuf;
kb->icount++;
tty_table[current].tty_events = 1;
force_timeout();
}
/* Else it doesn't fit - discard it. */
return 1; /* Reenable keyboard interrupt */
}
/*==========================================================================*
* kb_read *
*==========================================================================*/
PRIVATE void kb_read(tp)
tty_t *tp;
{
/* Process characters from the circular keyboard buffer. */
struct kb_s *kb;
char buf[3];
int scode;
unsigned ch;
kb = kb_addr();
tp = &tty_table[current]; /* always use the current console */
while (kb->icount > 0) {
scode = *kb->itail++; /* take one key scan code */
if (kb->itail == kb->ibuf + KB_IN_BYTES) kb->itail = kb->ibuf;
lock();
kb->icount--;
unlock();
/* Function keys are being used for debug dumps. */
if (func_key(scode)) continue;
/* Perform make/break processing. */
ch = make_break(scode);
if (ch <= 0xFF) {
/* A normal character. */
buf[0] = ch;
(void) in_process(tp, buf, 1);
} else
if (HOME <= ch && ch <= INSRT) {
/* An ASCII escape sequence generated by the numeric pad. */
buf[0] = ESC;
buf[1] = '[';
buf[2] = numpad_map[ch - HOME];
(void) in_process(tp, buf, 3);
} else
if (ch == ALEFT) {
/* Choose lower numbered console as current console. */
select_console(current - 1);
} else
if (ch == ARIGHT) {
/* Choose higher numbered console as current console. */
select_console(current + 1);
} else
if (AF1 <= ch && ch <= AF12) {
/* Alt-F1 is console, Alt-F2 is ttyc1, etc. */
select_console(ch - AF1);
}
}
}
/*===========================================================================*
* make_break *
*===========================================================================*/
PRIVATE unsigned make_break(scode)
int scode; /* scan code of key just struck or released */
{
/* This routine can handle keyboards that interrupt only on key depression,
* as well as keyboards that interrupt on key depression and key release.
* For efficiency, the interrupt routine filters out most key releases.
*/
int ch, make;
static int CAD_count = 0;
/* Check for CTRL-ALT-DEL, and if found, halt the computer. This would
* be better done in keyboard() in case TTY is hung, except control and
* alt are set in the high level code.
*/
if (control && (alt1 || alt2) && scode == DEL_SCAN)
{
if (++CAD_count == 3) wreboot(RBT_HALT);
cause_sig(INIT_PROC_NR, SIGABRT);
return -1;
}
/* High-order bit set on key release. */
make = (scode & 0200 ? 0 : 1); /* 0 = release, 1 = press */
ch = map_key(scode & 0177); /* map to ASCII */
switch (ch) {
case CTRL:
control = make;
ch = -1;
break;
case SHIFT:
shift = make;
ch = -1;
break;
case ALT:
if (make) {
if (esc) alt2 = 1; else alt1 = 1;
} else {
alt1 = alt2 = 0;
}
ch = -1;
break;
case CALOCK:
if (make && caps_off) {
capslock = 1 - capslock;
set_leds();
}
caps_off = 1 - make;
ch = -1;
break;
case NLOCK:
if (make && num_off) {
numlock = 1 - numlock;
set_leds();
}
num_off = 1 - make;
ch = -1;
break;
case SLOCK:
if (make & slock_off) {
slock = 1 - slock;
set_leds();
}
slock_off = 1 - make;
ch = -1;
break;
case EXTKEY:
esc = 1;
return(-1);
default:
if (!make) ch = -1;
}
esc = 0;
return(ch);
}
/*===========================================================================*
* set_leds *
*===========================================================================*/
PRIVATE void set_leds()
{
/* Set the LEDs on the caps lock and num lock keys */
unsigned leds;
if (!pc_at) return; /* PC/XT doesn't have LEDs */
/* encode LED bits */
leds = (slock << 0) | (numlock << 1) | (capslock << 2);
kb_wait(); /* wait for buffer empty */
out_byte(KEYBD, LED_CODE); /* prepare keyboard to accept LED values */
kb_ack(); /* wait for ack response */
kb_wait(); /* wait for buffer empty */
out_byte(KEYBD, leds); /* give keyboard LED values */
kb_ack(); /* wait for ack response */
}
/*==========================================================================*
* kb_wait *
*==========================================================================*/
PRIVATE int kb_wait()
{
/* Wait until the controller is ready; return zero if this times out. */
int retries;
retries = MAX_KB_BUSY_RETRIES + 1;
while (--retries != 0 && in_byte(KB_STATUS) & KB_BUSY)
; /* wait until not busy */
return(retries); /* nonzero if ready */
}
/*==========================================================================*
* kb_ack *
*==========================================================================*/
PRIVATE int kb_ack()
{
/* Wait until kbd acknowledges last command; return zero if this times out. */
int retries;
retries = MAX_KB_ACK_RETRIES + 1;
while (--retries != 0 && in_byte(KEYBD) != KB_ACK)
; /* wait for ack */
return(retries); /* nonzero if ack received */
}
/*===========================================================================*
* kb_init *
*===========================================================================*/
PUBLIC void kb_init(tp)
tty_t *tp;
{
/* Initialize the keyboard driver. */
register struct kb_s *kb;
/* Input function. */
tp->tty_devread = kb_read;
kb = kb_addr();
/* Set up input queue. */
kb->ihead = kb->itail = kb->ibuf;
/* Set initial values. */
caps_off = 1;
num_off = 1;
slock_off = 1;
esc = 0;
set_leds(); /* turn off numlock led */
scan_keyboard(); /* stop lockup from leftover keystroke */
put_irq_handler(KEYBOARD_IRQ, kbd_hw_int); /* set the interrupt handler */
enable_irq(KEYBOARD_IRQ); /* safe now everything initialised! */
}
/*===========================================================================*
* kbd_loadmap *
*===========================================================================*/
PUBLIC int kbd_loadmap(user_phys)
phys_bytes user_phys;
{
/* Load a new keymap. */
phys_copy(user_phys, vir2phys(keymap), (phys_bytes) sizeof(keymap));
return(OK);
}
/*===========================================================================*
* func_key *
*===========================================================================*/
PRIVATE int func_key(scode)
int scode; /* scan code for a function key */
{
/* This procedure traps function keys for debugging and control purposes. */
unsigned code;
code = map_key0(scode); /* first ignore modifiers */
if (code < F1 || code > F12) return(FALSE); /* not our job */
switch (map_key(scode)) { /* include modifiers */
case F1: p_dmp(); break; /* print process table */
case F2: map_dmp(); break; /* print memory map */
case F3: toggle_scroll(); break; /* hardware vs. software scrolling */
#if ENABLE_NETWORKING
case F5: dp_dump(); break; /* network statistics */
#endif
case CF7: sigchar(&tty_table[CONSOLE], SIGQUIT); break;
case CF8: sigchar(&tty_table[CONSOLE], SIGINT); break;
case CF9: sigchar(&tty_table[CONSOLE], SIGKILL); break;
default: return(FALSE);
}
return(TRUE);
}
/*==========================================================================*
* scan_keyboard *
*==========================================================================*/
PRIVATE int scan_keyboard()
{
/* Fetch the character from the keyboard hardware and acknowledge it. */
int code;
int val;
code = in_byte(KEYBD); /* get the scan code for the key struck */
val = in_byte(PORT_B); /* strobe the keyboard to ack the char */
out_byte(PORT_B, val | KBIT); /* strobe the bit high */
out_byte(PORT_B, val); /* now strobe it low */
return code;
}
/*==========================================================================*
* wreboot *
*==========================================================================*/
PUBLIC void wreboot(how)
int how; /* 0 = halt, 1 = reboot, 2 = panic!, ... */
{
/* Wait for keystrokes for printing debugging info and reboot. */
int quiet, code;
static u16_t magic = MEMCHECK_MAG;
struct tasktab *ttp;
/* Mask all interrupts. */
out_byte(INT_CTLMASK, ~0);
/* Tell several tasks to stop. */
cons_stop();
#if ENABLE_NETWORKING
dp8390_stop();
#endif
floppy_stop();
clock_stop();
if (how == RBT_HALT) {
printf("System Halted\n");
if (!mon_return) how = RBT_PANIC;
}
if (how == RBT_PANIC) {
/* A panic! */
printf("Hit ESC to reboot, F-keys for debug dumps\n");
(void) scan_keyboard(); /* ack any old input */
quiet = scan_keyboard();/* quiescent value (0 on PC, last code on AT)*/
for (;;) {
milli_delay(100); /* pause for a decisecond */
code = scan_keyboard();
if (code != quiet) {
/* A key has been pressed. */
if (code == ESC_SCAN) break; /* reboot if ESC typed */
(void) func_key(code); /* process function key */
quiet = scan_keyboard();
}
}
how = RBT_REBOOT;
}
if (how == RBT_REBOOT) printf("Rebooting\n");
if (mon_return && how != RBT_RESET) {
/* Reinitialize the interrupt controllers to the BIOS defaults. */
intr_init(0);
out_byte(INT_CTLMASK, 0);
out_byte(INT2_CTLMASK, 0);
/* Return to the boot monitor. */
if (how == RBT_HALT) {
reboot_code = vir2phys("");
} else
if (how == RBT_REBOOT) {
reboot_code = vir2phys("delay;boot");
}
level0(monitor);
}
/* Stop BIOS memory test. */
phys_copy(vir2phys(&magic), (phys_bytes) MEMCHECK_ADR,
(phys_bytes) sizeof(magic));
if (protected_mode) {
/* Use the AT keyboard controller to reset the processor.
* The A20 line is kept enabled in case this code is ever
* run from extended memory, and because some machines
* appear to drive the fake A20 high instead of low just
* after reset, leading to an illegal opode trap. This bug
* is more of a problem if the fake A20 is in use, as it
* would be if the keyboard reset were used for real mode.
*/
kb_wait();
out_byte(KB_COMMAND,
KB_PULSE_OUTPUT | (0x0F & ~(KB_GATE_A20 | KB_RESET)));
milli_delay(10);
/* If the nice method fails then do a reset. In protected
* mode this means a processor shutdown.
*/
printf("Hard reset...\n");
milli_delay(250);
}
/* In real mode, jumping to the reset address is good enough. */
level0(reset);
}