/*
* Copyright (c) 2017 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*
* Note that this driver is only intended to provide enough functionality
* for the diagnostics shell. As a result, some features, especially those
* that may not correctly be emulated for USB keyboards, will not be
* supported. This includes any communication with the keyboard itself.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* Intel 8042 I/O ports.
*/
#define ATKBD_PORT_DATA 0x60
#define ATKBD_PORT_STATUS 0x64
#define ATKBD_PORT_CMD 0x64
/*
* Status register bits.
*/
#define ATKBD_STATUS_OUT_FULL 0x01
#define ATKBD_STATUS_IN_FULL 0x02
#define ATKBD_STATUS_TIMEOUT_ERROR 0x40
#define ATKBD_STATUS_PARITY_ERROR 0x80
#define ATKBD_STATUS_ERROR (ATKBD_STATUS_PARITY_ERROR \
| ATKBD_STATUS_TIMEOUT_ERROR)
/*
* Controller commands.
*/
#define ATKBD_CMD_RDCONF 0x20
#define ATKBD_CMD_WRCONF 0x60
#define ATKBD_CMD_DIS2 0xa7
#define ATKBD_CMD_EN2 0xa8
#define ATKBD_CMD_DIS1 0xad
#define ATKBD_CMD_EN1 0xae
/*
* Controller configuration register bits.
*/
#define ATKBD_CONF_ENINT1 0x01
#define ATKBD_CONF_ENINT2 0x02
#define ATKBD_CONF_ENTRANS 0x40
/*
* Controller interrupt.
*
* This driver only supports keyboard input and doesn't use interrupt 12.
*/
#define ATKBD_INTR1 1
/*
* Key identifiers.
*
* Keys are obtained from static sparse tables, where undefined entries are
* initialized to 0. As a result, make that value an invalid key.
*/
enum atkbd_key_id {
ATKBD_KEY_INVALID = 0,
ATKBD_KEY_1 = 1,
ATKBD_KEY_2,
ATKBD_KEY_3,
ATKBD_KEY_4,
ATKBD_KEY_5,
ATKBD_KEY_6,
ATKBD_KEY_7,
ATKBD_KEY_8,
ATKBD_KEY_9,
ATKBD_KEY_0,
ATKBD_KEY_DASH,
ATKBD_KEY_EQUAL,
ATKBD_KEY_BACKSLASH,
ATKBD_KEY_BACKSPACE,
ATKBD_KEY_TAB,
ATKBD_KEY_Q,
ATKBD_KEY_W,
ATKBD_KEY_E,
ATKBD_KEY_R,
ATKBD_KEY_T,
ATKBD_KEY_Y,
ATKBD_KEY_U,
ATKBD_KEY_I,
ATKBD_KEY_O,
ATKBD_KEY_P,
ATKBD_KEY_OBRACKET,
ATKBD_KEY_CBRACKET,
ATKBD_KEY_ENTER,
ATKBD_KEY_CAPSLOCK,
ATKBD_KEY_A,
ATKBD_KEY_S,
ATKBD_KEY_D,
ATKBD_KEY_F,
ATKBD_KEY_G,
ATKBD_KEY_H,
ATKBD_KEY_J,
ATKBD_KEY_K,
ATKBD_KEY_L,
ATKBD_KEY_SEMICOLON,
ATKBD_KEY_QUOTE,
ATKBD_KEY_LSHIFT,
ATKBD_KEY_Z,
ATKBD_KEY_X,
ATKBD_KEY_C,
ATKBD_KEY_V,
ATKBD_KEY_B,
ATKBD_KEY_N,
ATKBD_KEY_M,
ATKBD_KEY_COMMA,
ATKBD_KEY_DOT,
ATKBD_KEY_SLASH,
ATKBD_KEY_RSHIFT,
ATKBD_KEY_LCTRL,
ATKBD_KEY_ALT,
ATKBD_KEY_SPACE,
ATKBD_KEY_ALTGR,
ATKBD_KEY_RCTRL,
ATKBD_KEY_INSERT,
ATKBD_KEY_DELETE,
ATKBD_KEY_HOME,
ATKBD_KEY_END,
ATKBD_KEY_PGUP,
ATKBD_KEY_PGDOWN,
ATKBD_KEY_LEFT,
ATKBD_KEY_BOTTOM,
ATKBD_KEY_RIGHT,
ATKBD_KEY_UP,
ATKBD_KEY_KP_NUMLOCK,
ATKBD_KEY_KP_SLASH,
ATKBD_KEY_KP_STAR,
ATKBD_KEY_KP_MINUS,
ATKBD_KEY_KP_HOME,
ATKBD_KEY_KP_UP,
ATKBD_KEY_KP_PGUP,
ATKBD_KEY_KP_PLUS,
ATKBD_KEY_KP_LEFT,
ATKBD_KEY_KP_5,
ATKBD_KEY_KP_RIGHT,
ATKBD_KEY_KP_END,
ATKBD_KEY_KP_BOTTOM,
ATKBD_KEY_KP_PGDOWN,
ATKBD_KEY_KP_ENTER,
ATKBD_KEY_KP_INS,
ATKBD_KEY_KP_DEL,
};
static const char atkbd_scroll_up[] = { CONSOLE_SCROLL_UP, '\0' };
static const char atkbd_scroll_down[] = { CONSOLE_SCROLL_DOWN, '\0' };
/*
* Key modifiers.
*/
#define ATKBD_KM_SHIFT 0x01 /* Shift / caps lock modifier applies */
#define ATKBD_KM_KP 0x02 /* Num lock modifier applies */
#define ATKBD_KM_CTL 0x04 /* Unmodified key is a control character */
struct atkbd_key {
int modifiers;
enum atkbd_key_id id;
};
/*
* Regular key table.
*
* Keys are indexed by their "scan code set 2" code. Undefined entries are
* initialized to 0, which is a known invalid identifier. Modifiers are used
* to select a different key->characters table, or key-specific processing
* in the case of control keys.
*/
static const struct atkbd_key atkbd_keys[] = {
[0x16] = { ATKBD_KM_SHIFT, ATKBD_KEY_1 },
[0x1e] = { ATKBD_KM_SHIFT, ATKBD_KEY_2 },
[0x26] = { ATKBD_KM_SHIFT, ATKBD_KEY_3 },
[0x25] = { ATKBD_KM_SHIFT, ATKBD_KEY_4 },
[0x2e] = { ATKBD_KM_SHIFT, ATKBD_KEY_5 },
[0x36] = { ATKBD_KM_SHIFT, ATKBD_KEY_6 },
[0x3d] = { ATKBD_KM_SHIFT, ATKBD_KEY_7 },
[0x3e] = { ATKBD_KM_SHIFT, ATKBD_KEY_8 },
[0x46] = { ATKBD_KM_SHIFT, ATKBD_KEY_9 },
[0x45] = { ATKBD_KM_SHIFT, ATKBD_KEY_0 },
[0x4e] = { ATKBD_KM_SHIFT, ATKBD_KEY_DASH },
[0x55] = { ATKBD_KM_SHIFT, ATKBD_KEY_EQUAL },
[0x5d] = { ATKBD_KM_SHIFT, ATKBD_KEY_BACKSLASH },
[0x66] = { 0, ATKBD_KEY_BACKSPACE },
[0x0d] = { 0, ATKBD_KEY_TAB },
[0x15] = { ATKBD_KM_SHIFT, ATKBD_KEY_Q },
[0x1d] = { ATKBD_KM_SHIFT, ATKBD_KEY_W },
[0x24] = { ATKBD_KM_SHIFT, ATKBD_KEY_E },
[0x2d] = { ATKBD_KM_SHIFT, ATKBD_KEY_R },
[0x2c] = { ATKBD_KM_SHIFT, ATKBD_KEY_T },
[0x35] = { ATKBD_KM_SHIFT, ATKBD_KEY_Y },
[0x3c] = { ATKBD_KM_SHIFT, ATKBD_KEY_U },
[0x43] = { ATKBD_KM_SHIFT, ATKBD_KEY_I },
[0x44] = { ATKBD_KM_SHIFT, ATKBD_KEY_O },
[0x4d] = { ATKBD_KM_SHIFT, ATKBD_KEY_P },
[0x54] = { ATKBD_KM_SHIFT, ATKBD_KEY_OBRACKET },
[0x5b] = { ATKBD_KM_SHIFT, ATKBD_KEY_CBRACKET },
[0x5a] = { 0, ATKBD_KEY_ENTER },
[0x58] = { ATKBD_KM_CTL, ATKBD_KEY_CAPSLOCK },
[0x1c] = { ATKBD_KM_SHIFT, ATKBD_KEY_A },
[0x1b] = { ATKBD_KM_SHIFT, ATKBD_KEY_S },
[0x23] = { ATKBD_KM_SHIFT, ATKBD_KEY_D },
[0x2b] = { ATKBD_KM_SHIFT, ATKBD_KEY_F },
[0x34] = { ATKBD_KM_SHIFT, ATKBD_KEY_G },
[0x33] = { ATKBD_KM_SHIFT, ATKBD_KEY_H },
[0x3b] = { ATKBD_KM_SHIFT, ATKBD_KEY_J },
[0x42] = { ATKBD_KM_SHIFT, ATKBD_KEY_K },
[0x4b] = { ATKBD_KM_SHIFT, ATKBD_KEY_L },
[0x4c] = { ATKBD_KM_SHIFT, ATKBD_KEY_SEMICOLON },
[0x52] = { ATKBD_KM_SHIFT, ATKBD_KEY_QUOTE },
[0x12] = { ATKBD_KM_CTL, ATKBD_KEY_LSHIFT },
[0x1a] = { ATKBD_KM_SHIFT, ATKBD_KEY_Z },
[0x22] = { ATKBD_KM_SHIFT, ATKBD_KEY_X },
[0x21] = { ATKBD_KM_SHIFT, ATKBD_KEY_C },
[0x2a] = { ATKBD_KM_SHIFT, ATKBD_KEY_V },
[0x32] = { ATKBD_KM_SHIFT, ATKBD_KEY_B },
[0x31] = { ATKBD_KM_SHIFT, ATKBD_KEY_N },
[0x3a] = { ATKBD_KM_SHIFT, ATKBD_KEY_M },
[0x41] = { ATKBD_KM_SHIFT, ATKBD_KEY_COMMA },
[0x49] = { ATKBD_KM_SHIFT, ATKBD_KEY_DOT },
[0x4a] = { ATKBD_KM_SHIFT, ATKBD_KEY_SLASH },
[0x59] = { ATKBD_KM_CTL, ATKBD_KEY_RSHIFT },
[0x14] = { ATKBD_KM_CTL, ATKBD_KEY_LCTRL },
[0x11] = { ATKBD_KM_CTL, ATKBD_KEY_ALT },
[0x29] = { 0, ATKBD_KEY_SPACE },
[0x77] = { ATKBD_KM_CTL, ATKBD_KEY_KP_NUMLOCK },
[0x7c] = { 0, ATKBD_KEY_KP_STAR },
[0x7b] = { 0, ATKBD_KEY_KP_MINUS },
[0x6c] = { ATKBD_KM_KP, ATKBD_KEY_KP_HOME },
[0x75] = { ATKBD_KM_KP, ATKBD_KEY_KP_UP },
[0x7d] = { ATKBD_KM_KP, ATKBD_KEY_KP_PGUP },
[0x79] = { 0, ATKBD_KEY_KP_PLUS },
[0x6b] = { ATKBD_KM_KP, ATKBD_KEY_KP_LEFT },
[0x73] = { 0, ATKBD_KEY_KP_5 },
[0x74] = { ATKBD_KM_KP, ATKBD_KEY_KP_RIGHT },
[0x69] = { ATKBD_KM_KP, ATKBD_KEY_KP_END },
[0x72] = { ATKBD_KM_KP, ATKBD_KEY_KP_BOTTOM },
[0x7a] = { ATKBD_KM_KP, ATKBD_KEY_KP_PGDOWN },
[0x70] = { ATKBD_KM_CTL | ATKBD_KM_KP, ATKBD_KEY_KP_INS },
[0x71] = { ATKBD_KM_KP, ATKBD_KEY_KP_DEL },
};
/*
* Key table for e0 codes.
*
* This table is similar to the regular key table, except it is used if an
* e0 code was read in the input byte sequence.
*/
static const struct atkbd_key atkbd_e0_keys[] = {
[0x11] = { ATKBD_KM_CTL, ATKBD_KEY_ALTGR },
[0x14] = { ATKBD_KM_CTL, ATKBD_KEY_RCTRL },
[0x70] = { ATKBD_KM_CTL, ATKBD_KEY_INSERT },
[0x71] = { 0, ATKBD_KEY_DELETE },
[0x6c] = { 0, ATKBD_KEY_HOME },
[0x69] = { 0, ATKBD_KEY_END },
[0x7d] = { ATKBD_KM_SHIFT, ATKBD_KEY_PGUP },
[0x7a] = { ATKBD_KM_SHIFT, ATKBD_KEY_PGDOWN },
[0x6b] = { ATKBD_KM_CTL, ATKBD_KEY_LEFT },
[0x72] = { ATKBD_KM_CTL, ATKBD_KEY_BOTTOM },
[0x74] = { ATKBD_KM_CTL, ATKBD_KEY_RIGHT },
[0x75] = { ATKBD_KM_CTL, ATKBD_KEY_UP },
[0x4a] = { 0, ATKBD_KEY_KP_SLASH },
[0x5a] = { 0, ATKBD_KEY_KP_ENTER },
};
/*
* Key to characters table.
*
* This table is used for keys with no modifiers or if none of the modifiers
* apply at input time.
*/
static const char *atkbd_chars[] = {
[ATKBD_KEY_1] = "1",
[ATKBD_KEY_2] = "2",
[ATKBD_KEY_3] = "3",
[ATKBD_KEY_4] = "4",
[ATKBD_KEY_5] = "5",
[ATKBD_KEY_6] = "6",
[ATKBD_KEY_7] = "7",
[ATKBD_KEY_8] = "8",
[ATKBD_KEY_9] = "9",
[ATKBD_KEY_0] = "0",
[ATKBD_KEY_DASH] = "-",
[ATKBD_KEY_EQUAL] = "=",
[ATKBD_KEY_BACKSLASH] = "\\",
[ATKBD_KEY_BACKSPACE] = "\b",
[ATKBD_KEY_TAB] = "\t",
[ATKBD_KEY_Q] = "q",
[ATKBD_KEY_W] = "w",
[ATKBD_KEY_E] = "e",
[ATKBD_KEY_R] = "r",
[ATKBD_KEY_T] = "t",
[ATKBD_KEY_Y] = "y",
[ATKBD_KEY_U] = "u",
[ATKBD_KEY_I] = "i",
[ATKBD_KEY_O] = "o",
[ATKBD_KEY_P] = "p",
[ATKBD_KEY_OBRACKET] = "[",
[ATKBD_KEY_CBRACKET] = "]",
[ATKBD_KEY_ENTER] = "\n",
[ATKBD_KEY_A] = "a",
[ATKBD_KEY_S] = "s",
[ATKBD_KEY_D] = "d",
[ATKBD_KEY_F] = "f",
[ATKBD_KEY_G] = "g",
[ATKBD_KEY_H] = "h",
[ATKBD_KEY_J] = "j",
[ATKBD_KEY_K] = "k",
[ATKBD_KEY_L] = "l",
[ATKBD_KEY_SEMICOLON] = ";",
[ATKBD_KEY_QUOTE] = "'",
[ATKBD_KEY_Z] = "z",
[ATKBD_KEY_X] = "x",
[ATKBD_KEY_C] = "c",
[ATKBD_KEY_V] = "v",
[ATKBD_KEY_B] = "b",
[ATKBD_KEY_N] = "n",
[ATKBD_KEY_M] = "m",
[ATKBD_KEY_COMMA] = ",",
[ATKBD_KEY_DOT] = ".",
[ATKBD_KEY_SLASH] = "/",
[ATKBD_KEY_SPACE] = " ",
[ATKBD_KEY_DELETE] = "\e[3~",
[ATKBD_KEY_HOME] = "\e[H",
[ATKBD_KEY_END] = "\e[F",
[ATKBD_KEY_KP_SLASH] = "/",
[ATKBD_KEY_KP_STAR] = "*",
[ATKBD_KEY_KP_MINUS] = "-",
[ATKBD_KEY_KP_HOME] = "\e[H",
[ATKBD_KEY_KP_PLUS] = "+",
[ATKBD_KEY_KP_5] = "5",
[ATKBD_KEY_KP_END] = "\e[F",
[ATKBD_KEY_KP_ENTER] = "\n",
[ATKBD_KEY_KP_DEL] = "\e[3~",
};
/*
* Key to characters table when the shift modifier applies.
*/
static const char *atkbd_shift_chars[] = {
[ATKBD_KEY_1] = "!",
[ATKBD_KEY_2] = "@",
[ATKBD_KEY_3] = "#",
[ATKBD_KEY_4] = "$",
[ATKBD_KEY_5] = "%",
[ATKBD_KEY_6] = "^",
[ATKBD_KEY_7] = "&",
[ATKBD_KEY_8] = "*",
[ATKBD_KEY_9] = "(",
[ATKBD_KEY_0] = ")",
[ATKBD_KEY_DASH] = "_",
[ATKBD_KEY_EQUAL] = "+",
[ATKBD_KEY_BACKSLASH] = "|",
[ATKBD_KEY_Q] = "Q",
[ATKBD_KEY_W] = "W",
[ATKBD_KEY_E] = "E",
[ATKBD_KEY_R] = "R",
[ATKBD_KEY_T] = "T",
[ATKBD_KEY_Y] = "Y",
[ATKBD_KEY_U] = "U",
[ATKBD_KEY_I] = "I",
[ATKBD_KEY_O] = "O",
[ATKBD_KEY_P] = "P",
[ATKBD_KEY_OBRACKET] = "{",
[ATKBD_KEY_CBRACKET] = "}",
[ATKBD_KEY_A] = "A",
[ATKBD_KEY_S] = "S",
[ATKBD_KEY_D] = "D",
[ATKBD_KEY_F] = "F",
[ATKBD_KEY_G] = "G",
[ATKBD_KEY_H] = "H",
[ATKBD_KEY_J] = "J",
[ATKBD_KEY_K] = "K",
[ATKBD_KEY_L] = "L",
[ATKBD_KEY_SEMICOLON] = ":",
[ATKBD_KEY_QUOTE] = "\"",
[ATKBD_KEY_Z] = "Z",
[ATKBD_KEY_X] = "X",
[ATKBD_KEY_C] = "C",
[ATKBD_KEY_V] = "V",
[ATKBD_KEY_B] = "B",
[ATKBD_KEY_N] = "N",
[ATKBD_KEY_M] = "M",
[ATKBD_KEY_COMMA] = "<",
[ATKBD_KEY_DOT] = ">",
[ATKBD_KEY_SLASH] = "?",
[ATKBD_KEY_PGUP] = atkbd_scroll_up,
[ATKBD_KEY_PGDOWN] = atkbd_scroll_down,
};
/*
* Key to keypad characters table when the numlock modifier applies.
*/
static const char *atkbd_kp_chars[] = {
[ATKBD_KEY_KP_HOME] = "7",
[ATKBD_KEY_KP_UP] = "8",
[ATKBD_KEY_KP_PGUP] = "9",
[ATKBD_KEY_KP_LEFT] = "4",
[ATKBD_KEY_KP_RIGHT] = "6",
[ATKBD_KEY_KP_END] = "1",
[ATKBD_KEY_KP_BOTTOM] = "2",
[ATKBD_KEY_KP_PGDOWN] = "3",
[ATKBD_KEY_KP_INS] = "0",
[ATKBD_KEY_KP_DEL] = ".",
};
/*
* Global flags.
*/
#define ATKBD_KF_E0 0x01 /* Current sequence includes an 0xe0 byte */
#define ATKBD_KF_F0 0x02 /* Current sequence includes an 0xf0 byte,
which usually indicates a key release */
#define ATKBD_KF_LSHIFT 0x04 /* Left shift key is being pressed */
#define ATKBD_KF_RSHIFT 0x08 /* Right shift key is being pressed */
#define ATKBD_KF_NUMLOCK 0x10 /* Numlock key has been toggled on */
#define ATKBD_KF_CAPSLOCK 0x20 /* Caps lock key has been toggled on */
#define ATKBD_KF_SHIFT (ATKBD_KF_CAPSLOCK \
| ATKBD_KF_RSHIFT \
| ATKBD_KF_LSHIFT)
/*
* These flags are only accessed during interrupt handling and don't
* require additional synchronization.
*/
static unsigned int atkbd_flags;
static uint8_t
atkbd_read_data(void)
{
return io_read_byte(ATKBD_PORT_DATA);
}
static void
atkbd_write_data(uint8_t data)
{
io_write_byte(ATKBD_PORT_DATA, data);
}
static int
atkbd_read_status(bool check_out)
{
uint8_t status;
status = io_read_byte(ATKBD_PORT_STATUS);
/*
* XXX This case is rare enough that it can be used to identify the lack
* of hardware.
*/
if (status == 0xff) {
log_info("atkbd: no keyboard controller");
return ENODEV;
} else if (status & ATKBD_STATUS_PARITY_ERROR) {
log_err("atkbd: parity error");
return EIO;
} else if (status & ATKBD_STATUS_TIMEOUT_ERROR) {
log_err("atkbd: timeout error");
return ETIMEDOUT;
}
if (check_out) {
return (status & ATKBD_STATUS_OUT_FULL) ? 0 : EAGAIN;
} else {
return (status & ATKBD_STATUS_IN_FULL) ? EAGAIN : 0;
}
}
static void
atkbd_write_cmd(uint8_t cmd)
{
io_write_byte(ATKBD_PORT_CMD, cmd);
}
static int
atkbd_out_wait(void)
{
int error;
for (;;) {
error = atkbd_read_status(true);
if (error != EAGAIN) {
break;
}
}
return error;
}
static int
atkbd_in_wait(void)
{
int error;
for (;;) {
error = atkbd_read_status(false);
if (error != EAGAIN) {
break;
}
}
return error;
}
static int
atkbd_read(uint8_t *datap, bool wait)
{
int error;
if (wait) {
error = atkbd_out_wait();
} else {
error = atkbd_read_status(true);
}
if (error) {
return error;
}
*datap = atkbd_read_data();
return 0;
}
static int
atkbd_write(uint8_t data)
{
int error;
error = atkbd_in_wait();
if (error) {
return error;
}
atkbd_write_data(data);
return 0;
}
static int __init
atkbd_flush(void)
{
uint8_t byte;
int error;
do {
error = atkbd_read(&byte, false);
} while (!error);
if (error == EAGAIN) {
error = 0;
}
return error;
}
static int __init
atkbd_disable(void)
{
uint8_t byte;
int error;
atkbd_write_cmd(ATKBD_CMD_DIS1);
atkbd_write_cmd(ATKBD_CMD_DIS2);
atkbd_write_cmd(ATKBD_CMD_RDCONF);
error = atkbd_read(&byte, true);
if (error) {
return error;
}
byte &= ~(ATKBD_CONF_ENTRANS | ATKBD_CONF_ENINT2 | ATKBD_CONF_ENINT1);
atkbd_write_cmd(ATKBD_CMD_WRCONF);
return atkbd_write(byte);
}
static int __init
atkbd_enable(void)
{
uint8_t byte;
int error;
atkbd_write_cmd(ATKBD_CMD_EN1);
atkbd_write_cmd(ATKBD_CMD_RDCONF);
error = atkbd_read(&byte, true);
if (error) {
return error;
}
byte &= ~(ATKBD_CONF_ENTRANS | ATKBD_CONF_ENINT2);
byte |= ATKBD_CONF_ENINT1;
atkbd_write_cmd(ATKBD_CMD_WRCONF);
error = atkbd_write(byte);
if (error) {
return error;
}
return atkbd_flush();
}
static void
atkbd_toggle_numlock(void)
{
atkbd_flags ^= ATKBD_KF_NUMLOCK;
}
static void
atkbd_toggle_capslock(void)
{
atkbd_flags ^= ATKBD_KF_CAPSLOCK;
}
static void
atkbd_key_process_chars(const struct atkbd_key *key,
const char **chars, size_t size)
{
const char *s;
if (key->id >= size) {
return;
}
if (atkbd_flags & ATKBD_KF_F0) {
return;
}
s = chars[key->id];
if (s != NULL) {
atcons_intr(s);
}
}
static void
atkbd_key_process_shift(const struct atkbd_key *key)
{
atkbd_key_process_chars(key, atkbd_shift_chars,
ARRAY_SIZE(atkbd_shift_chars));
}
static void
atkbd_key_process_kp(const struct atkbd_key *key)
{
atkbd_key_process_chars(key, atkbd_kp_chars,
ARRAY_SIZE(atkbd_kp_chars));
}
static void
atkbd_key_process_ctl(const struct atkbd_key *key)
{
switch (key->id) {
case ATKBD_KEY_LSHIFT:
if (atkbd_flags & ATKBD_KF_F0) {
atkbd_flags &= ~ATKBD_KF_LSHIFT;
} else {
atkbd_flags |= ATKBD_KF_LSHIFT;
}
break;
case ATKBD_KEY_RSHIFT:
if (atkbd_flags & ATKBD_KF_F0) {
atkbd_flags &= ~ATKBD_KF_RSHIFT;
} else {
atkbd_flags |= ATKBD_KF_RSHIFT;
}
break;
case ATKBD_KEY_KP_NUMLOCK:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atkbd_toggle_numlock();
}
break;
case ATKBD_KEY_CAPSLOCK:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atkbd_toggle_capslock();
}
break;
case ATKBD_KEY_LEFT:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atcons_left();
}
break;
case ATKBD_KEY_BOTTOM:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atcons_bottom();
}
break;
case ATKBD_KEY_RIGHT:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atcons_right();
}
break;
case ATKBD_KEY_UP:
if (!(atkbd_flags & ATKBD_KF_F0)) {
atcons_up();
}
break;
default:
break;
}
}
static void
atkbd_key_process(const struct atkbd_key *key)
{
if (key->id == ATKBD_KEY_INVALID) {
goto out;
}
if ((key->modifiers & ATKBD_KM_SHIFT) && (atkbd_flags & ATKBD_KF_SHIFT)) {
atkbd_key_process_shift(key);
} else if ((key->modifiers & ATKBD_KM_KP)
&& (atkbd_flags & ATKBD_KF_NUMLOCK)) {
atkbd_key_process_kp(key);
} else if (key->modifiers & ATKBD_KM_CTL) {
atkbd_key_process_ctl(key);
} else {
atkbd_key_process_chars(key, atkbd_chars, ARRAY_SIZE(atkbd_chars));
}
out:
atkbd_flags &= ~ATKBD_KF_F0;
}
static void
atkbd_process_e0_code(uint8_t code)
{
if (code == 0xf0) {
atkbd_flags |= ATKBD_KF_F0;
return;
}
if (code >= ARRAY_SIZE(atkbd_e0_keys)) {
goto out;
}
atkbd_key_process(&atkbd_e0_keys[code]);
out:
atkbd_flags &= ~ATKBD_KF_E0;
}
static void
atkbd_process_code(uint8_t code)
{
if (code == 0xe0) {
atkbd_flags |= ATKBD_KF_E0;
return;
} else if (code == 0xf0) {
atkbd_flags |= ATKBD_KF_F0;
return;
}
if (code >= ARRAY_SIZE(atkbd_keys)) {
return;
}
atkbd_key_process(&atkbd_keys[code]);
}
static int
atkbd_intr(void *arg)
{
uint8_t code;
int error;
(void)arg;
for (;;) {
error = atkbd_read(&code, false);
if (error) {
return 0;
}
if (atkbd_flags & ATKBD_KF_E0) {
atkbd_process_e0_code(code);
} else {
atkbd_process_code(code);
}
}
return 0;
}
static int __init
atkbd_setup(void)
{
int error;
error = atkbd_flush();
if (error) {
return 0;
}
error = atkbd_disable();
if (error) {
return 0;
}
error = intr_register(ATKBD_INTR1, atkbd_intr, NULL);
if (error) {
log_err("atkbd: unable to register interrupt handler");
return 0;
}
error = atkbd_enable();
if (error) {
return 0;
}
return 0;
}
INIT_OP_DEFINE(atkbd_setup,
INIT_OP_DEP(atcons_bootstrap, true),
INIT_OP_DEP(intr_setup, true));