minix3/drivers/hid/pckbd/pckbd.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;
}