Goto sanos source index

//
// kbd.c
//
// Keyboard driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>

//
// Keyboard Ports
//

#define KBD_DATA        0x60    // I/O port for keyboard data
#define KBD_COMMAND     0x64    // I/O port for keyboard commands (write)
#define KBD_STATUS      0x64    // I/O port for keyboard status (read)

//
// Keyboard Controller Commands
//

#define KBD_CCMD_READ_MODE      0x20    // Read mode bits
#define KBD_CCMD_WRITE_MODE     0x60    // Write mode bits
#define KBD_CCMD_GET_VERSION    0xA1    // Get controller version
#define KBD_CCMD_MOUSE_DISABLE  0xA7    // Disable mouse interface
#define KBD_CCMD_MOUSE_ENABLE   0xA8    // Enable mouse interface
#define KBD_CCMD_TEST_MOUSE     0xA9    // Mouse interface test
#define KBD_CCMD_SELF_TEST      0xAA    // Controller self test
#define KBD_CCMD_KBD_TEST       0xAB    // Keyboard interface test
#define KBD_CCMD_KBD_DISABLE    0xAD    // Keyboard interface disable
#define KBD_CCMD_KBD_ENABLE     0xAE    // Keyboard interface enable
#define KBD_CCMD_WRITE_AUX_OBUF 0xD3    // Write to output buffer as if initiated by the auxiliary device
#define KBD_CCMD_WRITE_MOUSE    0xD4    // Write the following byte to the mouse

//
// Keyboard Commands
//

#define KBD_CMD_SET_LEDS        0xED    // Set keyboard leds
#define KBD_CMD_SET_RATE        0xF3    // Set typematic rate
#define KBD_CMD_ENABLE          0xF4    // Enable scanning
#define KBD_CMD_DISABLE         0xF5    // Disable scanning
#define KBD_CMD_RESET           0xFF    // Reset

//
// Keyboard Replies
//

#define KBD_REPLY_POR           0xAA    // Power on reset
#define KBD_REPLY_ACK           0xFA    // Command ACK
#define KBD_REPLY_RESEND        0xFE    // Command NACK, send command again

//
// Keyboard Status Register Bits
//

#define KBD_STAT_OBF            0x01    // Keyboard output buffer full
#define KBD_STAT_IBF            0x02    // Keyboard input buffer full
#define KBD_STAT_SELFTEST       0x04    // Self test successful
#define KBD_STAT_CMD            0x08    // Last write was a command write
#define KBD_STAT_UNLOCKED       0x10    // Zero if keyboard locked
#define KBD_STAT_MOUSE_OBF      0x20    // Mouse output buffer full
#define KBD_STAT_GTO            0x40    // General receive/xmit timeout
#define KBD_STAT_PERR           0x80    // Parity error

//
// Controller Mode Register Bits
//

#define KBD_MODE_KBD_INT        0x01    // Keyboard data generate IRQ1
#define KBD_MODE_MOUSE_INT      0x02    // Mouse data generate IRQ12
#define KBD_MODE_SYS            0x04    // System flag
#define KBD_MODE_NO_KEYLOCK     0x08    // The keylock doesn't affect the keyboard if set
#define KBD_MODE_DISABLE_KBD    0x10    // Disable keyboard interface
#define KBD_MODE_DISABLE_MOUSE  0x20    // Disable mouse interface
#define KBD_MODE_KCC            0x40    // Scan code conversion to PC format
#define KBD_MODE_RFU            0x80

//
// Keyboard LEDs
//
#define LED_NUM_LOCK            2
#define LED_SCROLL_LOCK         1
#define LED_CAPS_LOCK           4

//
// Control Keys
//

#define CK_LSHIFT               0x01
#define CK_LALT                 0x02
#define CK_LCTRL                0x04
#define CK_RSHIFT               0x10
#define CK_RALT                 0x20
#define CK_RCTRL                0x40

#define KEYBOARD_BUFFER_SIZE    256
#define KEYBOARD_TIMEOUT        100000

struct keytable *keytables[MAX_KEYTABLES];

// Keyboard tables

#include "kbdext.h"
#include "kbdus.h"
#include "kbddk.h"
#include "kbduk.h"
#include "kbdfr.h"

// Keyboard status

unsigned char led_status = 0;
unsigned char control_keys = 0;
int ctrl_alt_del_enabled = 1;
int keymap = 0;
int ext = 0;

struct interrupt kbdintr;
struct dpc kbddpc;
struct sem kbdsem;
dev_t kbddev = -1;

// Circular keyboard buffer

unsigned char keyboard_buffer[KEYBOARD_BUFFER_SIZE];
int keyboard_buffer_in = 0;
int keyboard_buffer_out = 0;

//
// Wait for keyboard ready
//

static void kbd_wait() {
  int tmo = KEYBOARD_TIMEOUT;

  while (inp(KBD_STATUS) & KBD_STAT_IBF) {
    if (--tmo == 0) {
      kprintf("kbd: busy timeout\n");
      return;
    }

    udelay(1);
  }
}

//
// Read data from keyboard
//

static int kbd_read_data() {
  unsigned char status;

  status = inp(KBD_STATUS);
  if (status & KBD_STAT_OBF) {
    unsigned char data = inp(KBD_DATA);

    if (status & (KBD_STAT_GTO | KBD_STAT_PERR)) {
      return -EIO;
    } else {
      return data;
    }
  } else {
    return -EAGAIN;
  }
}

//
// Wait for data from keyboard
//

static int kbd_wait_for_data() {
  int tmo = KEYBOARD_TIMEOUT;

  while (1) {
    int rc = kbd_read_data();
    if (rc >= 0) return rc;

    if (--tmo == 0) {
      //kprintf("kbd: read timeout\n");
      return -ETIMEOUT;
    }

    udelay(1);
  }
}

//
// Write data to keyboard
//

static void kbd_write_data(unsigned char data) {
  outp(KBD_DATA, data);
}

//
// Write command to keyboard
//

static void kbd_write_command(unsigned char cmd) {
  outp(KBD_COMMAND, cmd);
}

//
// Reboot machine
//

void kbd_reboot() {
  kbd_wait();
  kbd_write_command(0xFE);
  cli();
  halt();
}               

//
// Set keyboard LEDs
//

static void setleds() {
  kbd_write_data(KBD_CMD_SET_LEDS);
  kbd_wait();
  kbd_write_data(led_status);
  kbd_wait();
}

//
// Insert into keyboard buffer
//

static void insert_key(unsigned char ch) {
  if (((keyboard_buffer_in + 1) & (KEYBOARD_BUFFER_SIZE - 1)) != keyboard_buffer_out) {
    keyboard_buffer[keyboard_buffer_in] = ch;
    keyboard_buffer_in = (keyboard_buffer_in + 1) & (KEYBOARD_BUFFER_SIZE - 1);
    release_sem(&kbdsem, 1);
    dev_setevt(kbddev, IOEVT_READ);
  }
}

//
// Process keyboard scancode
//

static void process_scancode(unsigned int scancode) {
  unsigned int keycode = 0;
  struct keytable *kt;
  int state;

  // In scancode mode just insert scancode
  if (keymap == -1) {
    insert_key((unsigned char) scancode);
    return;
  }

  // Get keytable
  kt = keytables[keymap];
  if (!kt) return;

  // Extended scancode
  if (scancode == 0xE0) {
    ext = 1;
    return;
  }

  //kprintf("scancode %02X %s%s\n", scancode & 0x7F, scancode & 0x80 ? "break" : "make", ext ? " ext" : "");

  // Ctrl-Alt-SysRq
  if ((control_keys & (CK_LCTRL | CK_LALT)) && scancode == 0xD4) {
    dbg_break();
    return;
  }

  // Ctrl-Alt-Del
  if ((control_keys & (CK_LCTRL | CK_LALT)) && scancode == 0x53) {
    if (ctrl_alt_del_enabled) reboot();
  }

  // LED keys, i.e. scroll lock, num lock, and caps lock
  if (scancode == 0x3A) {
    // Caps lock
    led_status ^= LED_CAPS_LOCK;
    setleds();
  }

  if (scancode == 0x45) {
    // Num lock
    led_status ^= LED_NUM_LOCK;
    setleds();
  }

  if (scancode == 0x46) {
    // Scroll lock
    led_status ^= LED_SCROLL_LOCK;
    setleds();
  }

  // Ctrl keys
  if (!ext && scancode == 0x1D) control_keys |= CK_LCTRL;
  if (!ext && scancode == (0x1D | 0x80)) control_keys &= ~CK_LCTRL;

  if (ext && scancode == 0x1D) control_keys |= CK_RCTRL;
  if (ext && scancode == (0x1D | 0x80)) control_keys &= ~CK_RCTRL;

  // Shift keys
  if (scancode == 0x2A) control_keys |= CK_LSHIFT;
  if (scancode == (0x2A | 0x80)) control_keys &= ~CK_LSHIFT;

  if (scancode == 0x36) control_keys |= CK_RSHIFT;
  if (scancode == (0x36 | 0x80)) control_keys &= ~CK_RSHIFT;

  // Alt key
  if (!ext && scancode == 0x38) control_keys |= CK_LALT;
  if (!ext && scancode == 0x80 + 0x38) control_keys &= ~CK_LALT;
          
  // AltGr key
  if (ext && scancode == 0x38) control_keys |= CK_RALT;
  if (ext && scancode == (0x38 | 0x80)) control_keys &= ~CK_RALT;

  if (scancode < MAX_SCANCODES) {
    if ((control_keys & (CK_LSHIFT | CK_RSHIFT)) && (led_status & LED_CAPS_LOCK)) {
      state = KBSTATE_SHIFTCAPS;
    } else if ((control_keys & CK_LSHIFT) && (control_keys & CK_LCTRL)) {
      state = KBSTATE_SHIFTCTRL;
    } else if (control_keys & (CK_LSHIFT | CK_RSHIFT)) {
      state = KBSTATE_SHIFT;
    } else if (control_keys & (CK_LCTRL | CK_RCTRL)) {
      state = KBSTATE_CTRL;
    } else if (control_keys & CK_LALT) {
      state = KBSTATE_ALT;
    } else if (control_keys & CK_RALT) {
      state = KBSTATE_ALTGR;
    } else if ((control_keys & (CK_LSHIFT | CK_RSHIFT)) && (led_status & LED_NUM_LOCK)) {
      state = KBSTATE_SHIFTNUM;
    } else if (led_status & LED_CAPS_LOCK) {
      state = KBSTATE_CAPSLOCK;
    } else if (led_status & LED_NUM_LOCK) {
      state = KBSTATE_NUMLOCK;
    } else if (control_keys == 0) {
      state = KBSTATE_NORMAL;
    }
    //kprintf("(%d,%x)", state, control_keys);

    if (ext) {
      keycode = extkeys[scancode][state];
    } else {
      keycode = kt->keys[scancode][state];
    }

    if (keycode != 0) {
      if (keycode <= 0xFF) {
        insert_key((unsigned char) keycode);
      } else {
        insert_key((unsigned char) (keycode & 0xFF));
        insert_key((unsigned char) (keycode >> 8));
      }
    }
  }

  ext = 0;
}

//
// Keyboard DPC
//

static void keyb_dpc(void *arg) {
  unsigned int scancode;
  unsigned char status;

  while ((status = inp(KBD_STATUS)) & KBD_STAT_OBF) {
    // Get next scancode from keyboard
    scancode = inp(KBD_DATA) & 0xFF;
    //kprintf("key %d\n", scancode);

    // Process scan code
    process_scancode(scancode);
  }

  eoi(IRQ_KBD);
}

//
// Keyboard interrupt handler
//

int keyboard_handler(struct context *ctxt, void *arg) {
  queue_irq_dpc(&kbddpc, keyb_dpc, NULL);

  return 0;
}

//
// Get one character from keyboard
//

int getch(unsigned int timeout) {
  unsigned char ch;
  int rc;

  rc = wait_for_one_object(&kbdsem, timeout, 1);
  if (rc < 0) return rc;

  ch = keyboard_buffer[keyboard_buffer_out];
  keyboard_buffer_out = (keyboard_buffer_out + 1) & (KEYBOARD_BUFFER_SIZE - 1);

  if (keyboard_buffer_in == keyboard_buffer_out) {
    dev_clrevt(kbddev, IOEVT_READ);
  }

  return ch;
}

//
// Test for any keyboard input.
// Returns 0 if keyboard buffer is empty, else returns 1.
//

int kbhit() {
  return keyboard_buffer_in != keyboard_buffer_out;
}

//
// Reset keyboard hardware
//

int reset_keyboard() {
  int status;

  // Test the keyboard interface
  kbd_wait();
  kbd_write_command(KBD_CCMD_SELF_TEST);
  if (kbd_wait_for_data() != 0x55) {
    kprintf(KERN_WARNING "kbd: Keyboard failed self test\n");
    return -EIO;
  }
  
  // Perform a keyboard interface test. This causes the controller
  // to test the keyboard clock and data lines.
  kbd_wait();
  kbd_write_command(KBD_CCMD_KBD_TEST);
  if (kbd_wait_for_data() != 0x00) {
    kprintf(KERN_WARNING "kbd: Keyboard interface failed self test\n");
    return -EIO;
  }

  // Enable the keyboard by allowing the keyboard clock to run
  kbd_wait();
  kbd_write_command(KBD_CCMD_KBD_ENABLE);

  // Reset keyboard. If the read times out then the assumption is that no keyboard is
  // plugged into the machine.
  while (1) {
    kbd_wait();
    kbd_write_data(KBD_CMD_RESET);
    status = kbd_wait_for_data();
    if (status == KBD_REPLY_ACK) break;
    if (status != KBD_REPLY_RESEND) {
      kprintf(KERN_ERR  "kbd: Keyboard reset failed, no ACK\n");
      return -EIO;
    }
  }

  if ((status = kbd_wait_for_data()) != KBD_REPLY_POR) {
    kprintf(KERN_ERR  "kbd: Keyboard reset failed, no POR (%d)\n", status);
    return -EIO;
  }

  // Set keyboard controller mode. During this, the keyboard should be
  // in the disabled state.
  while (1) {
    kbd_wait();
    kbd_write_data(KBD_CMD_DISABLE);
    status = kbd_wait_for_data();
    if (status == KBD_REPLY_ACK) break;
    if (status != KBD_REPLY_RESEND) {
      kprintf(KERN_ERR  "kbd: Disable keyboard: no ACK\n");
      return -EIO;
    }
  }

  kbd_wait();
  kbd_write_command(KBD_CCMD_WRITE_MODE);

  kbd_wait();
  kbd_write_data(KBD_MODE_KBD_INT | KBD_MODE_SYS | KBD_MODE_DISABLE_MOUSE | KBD_MODE_KCC);

  kbd_wait();
  kbd_write_data(KBD_CMD_ENABLE);
  if (kbd_wait_for_data() != KBD_REPLY_ACK) {
    kprintf("kbd: Enable keyboard: no ACK\n");
    return -EIO;
  }

  // Set the typematic rate to maximum
  kbd_wait();
  kbd_write_data(KBD_CMD_SET_RATE);
  if (kbd_wait_for_data() != KBD_REPLY_ACK) {
    kprintf(KERN_ERR "kbd: Set rate: no ACK\n");
    return -EIO;
  }
  
  kbd_wait();
  kbd_write_data(0x00);
  if (kbd_wait_for_data() != KBD_REPLY_ACK) {
    kprintf(KERN_ERR "kbd: Set rate: no ACK\n");
    return -EIO;
  }

  // Set all keyboard LEDs off
  led_status = 0; 
  setleds();

  return 0;
}

//
// Change keyboard map
//

int change_keyboard_map(char *kbdname) {
  int i;

  for (i = 0; i < MAX_KEYTABLES; i++) {
    if (keytables[i] && strcmp(keytables[i]->name, kbdname) == 0)  {
      keymap = i;
      return 0;
    }
  }

  keymap = 0;
  return -1;
}

int change_keyboard_map_id(int id) {
  if (id < -1 || id >= MAX_KEYTABLES || id != -1 && !keytables[id]) return -EINVAL;
  keymap = id;
  return 0;
}

//
// Initialize keyboard
//

void init_keyboard(dev_t devno, int reset) {
  char *kbdname;

  // Save keyboard device number
  kbddev = devno;

  // Initialize keyboard tables
  keytables[0] = &uskeys;
  keytables[1] = &dkkeys;
  keytables[2] = &ukkeys;
  keytables[3] = &frkeys;

  // Select keyboard table
  kbdname = get_property(krnlcfg, "kernel", "keyboard", "us");
  change_keyboard_map(kbdname);

  // Reset keyboard
  if (reset) reset_keyboard();

  // Setup keyboard interrupt handler
  init_dpc(&kbddpc);
  init_sem(&kbdsem, 0);
  register_interrupt(&kbdintr, INTR_KBD, keyboard_handler, NULL);
  enable_irq(IRQ_KBD);
}