477 lines
9.5 KiB
C
477 lines
9.5 KiB
C
|
/* Keyboard driver for PCs and ATs. */
|
||
|
#include <minix/drivers.h>
|
||
|
#include <minix/input.h>
|
||
|
#include <minix/inputdriver.h>
|
||
|
|
||
|
#include "pckbd.h"
|
||
|
|
||
|
/*
|
||
|
* Data that is to be sent to the keyboard. Each byte is ACKed by the keyboard.
|
||
|
* This is currently somewhat overpowered for its only purpose: setting LEDs.
|
||
|
*/
|
||
|
static struct kbdout {
|
||
|
unsigned char buf[KBD_OUT_BUFSZ];
|
||
|
int offset;
|
||
|
int avail;
|
||
|
int expect_ack;
|
||
|
} kbdout;
|
||
|
|
||
|
static int kbd_watchdog_set = 0;
|
||
|
static int kbd_alive = 1;
|
||
|
static minix_timer_t tmr_kbd_wd;
|
||
|
|
||
|
static int irq_hook_id = -1;
|
||
|
static int aux_irq_hook_id = -1;
|
||
|
|
||
|
static int kbd_state = 0;
|
||
|
|
||
|
static unsigned char aux_bytes[3];
|
||
|
static unsigned char aux_state = 0;
|
||
|
static int aux_counter = 0;
|
||
|
|
||
|
static void pckbd_leds(unsigned int);
|
||
|
static void pckbd_intr(unsigned int);
|
||
|
static void pckbd_alarm(clock_t);
|
||
|
|
||
|
static struct inputdriver pckbd_tab = {
|
||
|
.idr_leds = pckbd_leds,
|
||
|
.idr_intr = pckbd_intr,
|
||
|
.idr_alarm = pckbd_alarm
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The watchdog timer function, implementing all but the actual reset.
|
||
|
*/
|
||
|
static void
|
||
|
kbd_watchdog(minix_timer_t *UNUSED(tmrp))
|
||
|
{
|
||
|
kbd_watchdog_set = 0;
|
||
|
if (!kbdout.avail)
|
||
|
return; /* Watchdog is no longer needed */
|
||
|
|
||
|
if (!kbd_alive)
|
||
|
printf("PCKBD: watchdog should reset keyboard\n");
|
||
|
kbd_alive = 0;
|
||
|
|
||
|
set_timer(&tmr_kbd_wd, sys_hz(), kbd_watchdog, 0);
|
||
|
|
||
|
kbd_watchdog_set = 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send queued data to the keyboard.
|
||
|
*/
|
||
|
static void
|
||
|
kbd_send(void)
|
||
|
{
|
||
|
u32_t sb;
|
||
|
int r;
|
||
|
|
||
|
if (!kbdout.avail)
|
||
|
return;
|
||
|
if (kbdout.expect_ack)
|
||
|
return;
|
||
|
|
||
|
if ((r = sys_inb(KB_STATUS, &sb)) != OK)
|
||
|
printf("PCKBD: send sys_inb() failed (1): %d\n", r);
|
||
|
|
||
|
if (sb & (KB_OUT_FULL | KB_IN_FULL)) {
|
||
|
printf("PCKBD: not sending (1): sb = 0x%x\n", sb);
|
||
|
return;
|
||
|
}
|
||
|
micro_delay(KBC_IN_DELAY);
|
||
|
if ((r = sys_inb(KB_STATUS, &sb)) != OK)
|
||
|
printf("PCKBD: send sys_inb() failed (2): %d\n", r);
|
||
|
if (sb & (KB_OUT_FULL | KB_IN_FULL)) {
|
||
|
printf("PCKBD: not sending (2): sb = 0x%x\n", sb);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Okay, buffer is really empty */
|
||
|
if ((r = sys_outb(KEYBD, kbdout.buf[kbdout.offset])) != OK)
|
||
|
printf("PCKBD: send sys_outb() failed: %d\n", r);
|
||
|
kbdout.offset++;
|
||
|
kbdout.avail--;
|
||
|
kbdout.expect_ack = 1;
|
||
|
|
||
|
kbd_alive = 1;
|
||
|
if (kbd_watchdog_set) {
|
||
|
/* Set a watchdog timer for one second. */
|
||
|
set_timer(&tmr_kbd_wd, sys_hz(), kbd_watchdog, 0);
|
||
|
|
||
|
kbd_watchdog_set = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Try to obtain input from the keyboard.
|
||
|
*/
|
||
|
static int
|
||
|
scan_keyboard(unsigned char *bp, int *isauxp)
|
||
|
{
|
||
|
u32_t b, sb;
|
||
|
int r;
|
||
|
|
||
|
if ((r = sys_inb(KB_STATUS, &sb)) != OK) {
|
||
|
printf("PCKBD: scan sys_inb() failed (1): %d\n", r);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (!(sb & KB_OUT_FULL)) {
|
||
|
if (kbdout.avail && !kbdout.expect_ack)
|
||
|
kbd_send();
|
||
|
return FALSE;
|
||
|
}
|
||
|
if ((r = sys_inb(KEYBD, &b)) != OK) {
|
||
|
printf("PCKBD: scan sys_inb() failed (2): %d\n", r);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (!(sb & KB_AUX_BYTE) && b == KB_ACK && kbdout.expect_ack) {
|
||
|
kbdout.expect_ack = 0;
|
||
|
micro_delay(KBC_IN_DELAY);
|
||
|
kbd_send();
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (bp)
|
||
|
*bp = b;
|
||
|
if (isauxp)
|
||
|
*isauxp = !!(sb & KB_AUX_BYTE);
|
||
|
if (kbdout.avail && !kbdout.expect_ack) {
|
||
|
micro_delay(KBC_IN_DELAY);
|
||
|
kbd_send();
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Wait until the controller is ready. Return TRUE on success, FALSE on
|
||
|
* timeout. Since this may discard input, only use during initialization.
|
||
|
*/
|
||
|
static int
|
||
|
kb_wait(void)
|
||
|
{
|
||
|
spin_t spin;
|
||
|
u32_t status;
|
||
|
int r, isaux;
|
||
|
unsigned char byte;
|
||
|
|
||
|
SPIN_FOR(&spin, KBC_WAIT_TIME) {
|
||
|
if ((r = sys_inb(KB_STATUS, &status)) != OK)
|
||
|
printf("PCKBD: wait sys_inb() failed: %d\n", r);
|
||
|
if (status & KB_OUT_FULL)
|
||
|
(void) scan_keyboard(&byte, &isaux);
|
||
|
if (!(status & (KB_IN_FULL | KB_OUT_FULL)))
|
||
|
return TRUE; /* wait until ready */
|
||
|
}
|
||
|
|
||
|
printf("PCKBD: wait timeout\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set the LEDs on the caps, num, and scroll lock keys.
|
||
|
*/
|
||
|
static void
|
||
|
set_leds(unsigned char ledmask)
|
||
|
{
|
||
|
if (kbdout.avail == 0)
|
||
|
kbdout.offset = 0;
|
||
|
if (kbdout.offset + kbdout.avail + 2 > KBD_OUT_BUFSZ) {
|
||
|
/*
|
||
|
* The output buffer is full. Ignore this command. Reset the
|
||
|
* ACK flag.
|
||
|
*/
|
||
|
kbdout.expect_ack = 0;
|
||
|
} else {
|
||
|
kbdout.buf[kbdout.offset+kbdout.avail] = LED_CODE;
|
||
|
kbdout.buf[kbdout.offset+kbdout.avail+1] = ledmask;
|
||
|
kbdout.avail += 2;
|
||
|
}
|
||
|
if (!kbdout.expect_ack)
|
||
|
kbd_send();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send a command to the keyboard.
|
||
|
*/
|
||
|
static void
|
||
|
kbc_cmd0(int cmd)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
kb_wait();
|
||
|
if ((r = sys_outb(KB_COMMAND, cmd)) != OK)
|
||
|
printf("PCKBD: cmd0 sys_outb() failed: %d\n", r);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send a command to the keyboard, including data.
|
||
|
*/
|
||
|
static void
|
||
|
kbc_cmd1(int cmd, int data)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
kb_wait();
|
||
|
if ((r = sys_outb(KB_COMMAND, cmd)) != OK)
|
||
|
printf("PCKBD: cmd1 sys_outb() failed (1): %d\n", r);
|
||
|
kb_wait();
|
||
|
if ((r = sys_outb(KEYBD, data)) != OK)
|
||
|
printf("PCKBD: cmd1 sys_outb() failed (2): %d\n", r);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Wait at most one second for a byte from the keyboard or the controller.
|
||
|
*/
|
||
|
static int
|
||
|
kbc_read(void)
|
||
|
{
|
||
|
u32_t byte, status;
|
||
|
spin_t spin;
|
||
|
int r;
|
||
|
|
||
|
SPIN_FOR(&spin, KBC_READ_TIME) {
|
||
|
if ((r = sys_inb(KB_STATUS, &status)) != OK)
|
||
|
printf("PCKBD: read sys_inb() failed (1): %d\n", r);
|
||
|
if (status & KB_OUT_FULL) {
|
||
|
micro_delay(KBC_IN_DELAY);
|
||
|
if ((r = sys_inb(KEYBD, &byte)) != OK)
|
||
|
printf("PCKBD: read sys_inb() failed (2): "
|
||
|
"%d\n", r);
|
||
|
if (status & KB_AUX_BYTE)
|
||
|
printf("PCKBD: read got aux 0x%x\n", byte);
|
||
|
return byte;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
panic("kbc_read failed to complete");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize the keyboard hardware.
|
||
|
*/
|
||
|
static void
|
||
|
kb_init(void)
|
||
|
{
|
||
|
int r, ccb;
|
||
|
|
||
|
/* Discard leftover keystroke. */
|
||
|
scan_keyboard(NULL, NULL);
|
||
|
|
||
|
/* Set interrupt handler and enable keyboard IRQ. */
|
||
|
irq_hook_id = KEYBOARD_IRQ; /* id to be returned on interrupt */
|
||
|
r = sys_irqsetpolicy(KEYBOARD_IRQ, IRQ_REENABLE, &irq_hook_id);
|
||
|
if (r != OK)
|
||
|
panic("Couldn't set keyboard IRQ policy: %d", r);
|
||
|
if ((r = sys_irqenable(&irq_hook_id)) != OK)
|
||
|
panic("Couldn't enable keyboard IRQs: %d", r);
|
||
|
|
||
|
/* Set AUX interrupt handler and enable AUX IRQ. */
|
||
|
aux_irq_hook_id = KBD_AUX_IRQ; /* id to be returned on interrupt */
|
||
|
r = sys_irqsetpolicy(KBD_AUX_IRQ, IRQ_REENABLE, &aux_irq_hook_id);
|
||
|
if (r != OK)
|
||
|
panic("Couldn't set AUX IRQ policy: %d", r);
|
||
|
if ((r = sys_irqenable(&aux_irq_hook_id)) != OK)
|
||
|
panic("Couldn't enable AUX IRQs: %d", r);
|
||
|
|
||
|
/* Disable the keyboard and AUX. */
|
||
|
kbc_cmd0(KBC_DI_KBD);
|
||
|
kbc_cmd0(KBC_DI_AUX);
|
||
|
|
||
|
/* Get the current configuration byte. */
|
||
|
kbc_cmd0(KBC_RD_RAM_CCB);
|
||
|
ccb = kbc_read();
|
||
|
|
||
|
/* Enable both interrupts. */
|
||
|
kbc_cmd1(KBC_WR_RAM_CCB, ccb | 3);
|
||
|
|
||
|
/* Re-enable the keyboard device. */
|
||
|
kbc_cmd0(KBC_EN_KBD);
|
||
|
|
||
|
/* Enable the AUX device. */
|
||
|
kbc_cmd0(KBC_EN_AUX);
|
||
|
|
||
|
/* Set the initial LED state. */
|
||
|
kb_wait();
|
||
|
|
||
|
set_leds(0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Process a keyboard scancode.
|
||
|
*/
|
||
|
static void
|
||
|
kbd_process(unsigned char scode)
|
||
|
{
|
||
|
int press, index, page, code;
|
||
|
|
||
|
press = !(scode & SCAN_RELEASE) ? INPUT_PRESS : INPUT_RELEASE;
|
||
|
index = scode & ~SCAN_RELEASE;
|
||
|
|
||
|
switch (kbd_state) {
|
||
|
case 1:
|
||
|
page = scanmap_escaped[index].page;
|
||
|
code = scanmap_escaped[index].code;
|
||
|
break;
|
||
|
case 2:
|
||
|
kbd_state = (index == SCAN_CTRL) ? 3 : 0;
|
||
|
return;
|
||
|
case 3:
|
||
|
if (index == SCAN_NUMLOCK) {
|
||
|
page = INPUT_PAGE_KEY;
|
||
|
code = INPUT_KEY_PAUSE;
|
||
|
break;
|
||
|
}
|
||
|
/* FALLTHROUGH */
|
||
|
default:
|
||
|
switch (scode) {
|
||
|
case SCAN_EXT0:
|
||
|
kbd_state = 1;
|
||
|
return;
|
||
|
case SCAN_EXT1:
|
||
|
kbd_state = 2;
|
||
|
return;
|
||
|
}
|
||
|
page = scanmap_normal[index].page;
|
||
|
code = scanmap_normal[index].code;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (page)
|
||
|
inputdriver_send_event(FALSE /*mouse*/, page, code, press, 0);
|
||
|
|
||
|
kbd_state = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Process an auxiliary (mouse) scancode.
|
||
|
*/
|
||
|
static void
|
||
|
kbdaux_process(unsigned char scode)
|
||
|
{
|
||
|
u32_t delta;
|
||
|
int i;
|
||
|
|
||
|
if (aux_counter == 0 && !(scode & 0x08))
|
||
|
return; /* resync */
|
||
|
|
||
|
aux_bytes[aux_counter++] = scode;
|
||
|
|
||
|
if (aux_counter < 3)
|
||
|
return; /* need more first */
|
||
|
|
||
|
aux_counter = 0;
|
||
|
|
||
|
/* Send an event for each button state change. */
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
if ((aux_state ^ aux_bytes[0]) & (1 << i)) {
|
||
|
aux_state ^= (1 << i);
|
||
|
|
||
|
inputdriver_send_event(TRUE /*mouse*/,
|
||
|
INPUT_PAGE_BUTTON, INPUT_BUTTON_1 + i,
|
||
|
aux_state & (1 << i), 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Send an event for each relative mouse movement, X and/or Y. */
|
||
|
for (i = 0; i < 2; i++) {
|
||
|
delta = aux_bytes[1 + i];
|
||
|
if (delta != 0) {
|
||
|
if (aux_bytes[0] & (0x10 << i))
|
||
|
delta |= 0xFFFFFF00; /* make signed */
|
||
|
|
||
|
inputdriver_send_event(TRUE /*mouse*/, INPUT_PAGE_GD,
|
||
|
!i ? INPUT_GD_X : INPUT_GD_Y, delta,
|
||
|
INPUT_FLAG_REL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set keyboard LEDs.
|
||
|
*/
|
||
|
static void
|
||
|
pckbd_leds(unsigned int leds)
|
||
|
{
|
||
|
unsigned char b;
|
||
|
|
||
|
b = 0;
|
||
|
if (leds & (1 << INPUT_LED_NUMLOCK)) b |= LED_NUM_LOCK;
|
||
|
if (leds & (1 << INPUT_LED_CAPSLOCK)) b |= LED_CAPS_LOCK;
|
||
|
if (leds & (1 << INPUT_LED_SCROLLLOCK)) b |= LED_SCROLL_LOCK;
|
||
|
|
||
|
set_leds(b);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Process a keyboard interrupt.
|
||
|
*/
|
||
|
static void
|
||
|
pckbd_intr(unsigned int UNUSED(mask))
|
||
|
{
|
||
|
unsigned char scode;
|
||
|
int isaux;
|
||
|
|
||
|
/* Fetch a character from the keyboard hardware and acknowledge it. */
|
||
|
if (!scan_keyboard(&scode, &isaux))
|
||
|
return;
|
||
|
|
||
|
if (!isaux) {
|
||
|
/* A keyboard key press or release. */
|
||
|
kbd_process(scode);
|
||
|
} else {
|
||
|
/* A mouse event. */
|
||
|
kbdaux_process(scode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Process a timer signal.
|
||
|
*/
|
||
|
static void
|
||
|
pckbd_alarm(clock_t stamp)
|
||
|
{
|
||
|
expire_timers(stamp);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize the driver.
|
||
|
*/
|
||
|
static int
|
||
|
pckbd_init(int UNUSED(type), sef_init_info_t *UNUSED(info))
|
||
|
{
|
||
|
/* Initialize the watchdog timer. */
|
||
|
init_timer(&tmr_kbd_wd);
|
||
|
|
||
|
/* Initialize the keyboard. */
|
||
|
kb_init();
|
||
|
|
||
|
/* Announce the driver's presence. */
|
||
|
inputdriver_announce(INPUT_DEV_KBD | INPUT_DEV_MOUSE);
|
||
|
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set callback routines and let SEF initialize.
|
||
|
*/
|
||
|
static void
|
||
|
pckbd_startup(void)
|
||
|
{
|
||
|
sef_setcb_init_fresh(pckbd_init);
|
||
|
|
||
|
sef_startup();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* PC keyboard/mouse driver task.
|
||
|
*/
|
||
|
int
|
||
|
main(void)
|
||
|
{
|
||
|
pckbd_startup();
|
||
|
|
||
|
inputdriver_task(&pckbd_tab);
|
||
|
|
||
|
return 0;
|
||
|
}
|