// Copyright 2016 The Fuchsia Authors // Copyright (c) 2009 Corey Tabaka // Copyright (c) 2016 Travis Geiselbrecht // // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT #include #include "platform_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOCAL_TRACE 0 static inline uint8_t i8042_read_data(void) { return inp(I8042_DATA_REG); } static inline uint8_t i8042_read_status(void) { return inp(I8042_STATUS_REG); } static inline void i8042_write_data(uint8_t val) { outp(I8042_DATA_REG, val); } static inline void i8042_write_command(uint8_t val) { outp(I8042_COMMAND_REG, val); } /* * timeout in milliseconds */ #define I8042_CTL_TIMEOUT 500 /* * status register bits */ #define I8042_STR_PARITY 0x80 #define I8042_STR_TIMEOUT 0x40 #define I8042_STR_AUXDATA 0x20 #define I8042_STR_KEYLOCK 0x10 #define I8042_STR_CMDDAT 0x08 #define I8042_STR_MUXERR 0x04 #define I8042_STR_IBF 0x02 #define I8042_STR_OBF 0x01 /* * control register bits */ #define I8042_CTR_KBDINT 0x01 #define I8042_CTR_AUXINT 0x02 #define I8042_CTR_IGNKEYLK 0x08 #define I8042_CTR_KBDDIS 0x10 #define I8042_CTR_AUXDIS 0x20 #define I8042_CTR_XLATE 0x40 /* * commands */ #define I8042_CMD_CTL_RCTR 0x0120 #define I8042_CMD_CTL_WCTR 0x1060 #define I8042_CMD_CTL_TEST 0x01aa #define I8042_CMD_KBD_DIS 0x00ad #define I8042_CMD_KBD_EN 0x00ae #define I8042_CMD_PULSE_RESET 0x00fe #define I8042_CMD_KBD_TEST 0x01ab #define I8042_CMD_KBD_MODE 0x01f0 /* * used for flushing buffers. the i8042 internal buffer shoudn't exceed this. */ #define I8042_BUFFER_LENGTH 32 /* extended keys that aren't pure ascii */ enum extended_keys { KEY_RETURN = 0x80, KEY_ESC, KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, KEY_CAPSLOCK, KEY_LWIN, KEY_RWIN, KEY_MENU, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_PRTSCRN, KEY_SCRLOCK, KEY_PAUSE, KEY_TAB, KEY_BACKSPACE, KEY_INS, KEY_DEL, KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDN, KEY_ARROW_UP, KEY_ARROW_DOWN, KEY_ARROW_LEFT, KEY_ARROW_RIGHT, KEY_PAD_NUMLOCK, KEY_PAD_DIVIDE, KEY_PAD_MULTIPLY, KEY_PAD_MINUS, KEY_PAD_PLUS, KEY_PAD_ENTER, KEY_PAD_PERIOD, KEY_PAD_0, KEY_PAD_1, KEY_PAD_2, KEY_PAD_3, KEY_PAD_4, KEY_PAD_5, KEY_PAD_6, KEY_PAD_7, KEY_PAD_8, KEY_PAD_9, _KEY_LAST, }; static_assert(_KEY_LAST < 0x100, ""); /* scancode translation tables */ const uint8_t pc_keymap_set1_lower[128] = { /* 0x00 */ 0, KEY_ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', KEY_BACKSPACE, KEY_TAB, /* 0x10 */ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', KEY_RETURN, KEY_LCTRL, 'a', 's', /* 0x20 */ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', KEY_LSHIFT, '\\', 'z', 'x', 'c', 'v', /* 0x30 */ 'b', 'n', 'm', ',', '.', '/', KEY_RSHIFT, '*', KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, /* 0x40 */ KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_PAD_NUMLOCK, KEY_SCRLOCK, KEY_PAD_7, KEY_PAD_8, KEY_PAD_9, KEY_PAD_MINUS, KEY_PAD_4, KEY_PAD_5, KEY_PAD_6, KEY_PAD_PLUS, KEY_PAD_1, /* 0x50 */ KEY_PAD_2, KEY_PAD_3, KEY_PAD_0, KEY_PAD_PERIOD, 0, 0, 0, KEY_F11, KEY_F12, 0, 0, 0, 0, 0, 0, 0, }; const uint8_t pc_keymap_set1_upper[128] = { /* 0x00 */ 0, KEY_ESC, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', KEY_BACKSPACE, KEY_TAB, /* 0x10 */ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', KEY_RETURN, KEY_LCTRL, 'A', 'S', /* 0x20 */ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', KEY_LSHIFT, '|', 'Z', 'X', 'C', 'V', /* 0x30 */ 'B', 'N', 'M', '<', '>', '?', KEY_RSHIFT, '*', KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, /* 0x40 */ KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_PAD_NUMLOCK, KEY_SCRLOCK, KEY_PAD_7, KEY_PAD_8, KEY_PAD_9, KEY_PAD_MINUS, KEY_PAD_4, KEY_PAD_5, KEY_PAD_6, KEY_PAD_PLUS, KEY_PAD_1, /* 0x50 */ KEY_PAD_2, KEY_PAD_3, KEY_PAD_0, KEY_PAD_PERIOD, 0, 0, 0, KEY_F11, KEY_F12, 0, 0, 0, 0, 0, 0, 0, }; const uint8_t pc_keymap_set1_e0[128] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PAD_ENTER, KEY_RCTRL, 0, 0, /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 0, 0, 0, 0, KEY_PAD_DIVIDE, 0, KEY_PRTSCRN, KEY_RALT, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, KEY_HOME, KEY_ARROW_UP, KEY_PGUP, 0, KEY_ARROW_LEFT, 0, KEY_ARROW_RIGHT, 0, KEY_END, /* 0x50 */ KEY_ARROW_DOWN, KEY_PGDN, KEY_INS, 0, 0, 0, 0, 0, 0, 0, 0, KEY_LWIN, KEY_RWIN, KEY_MENU, 0, 0}; /* * state key flags */ static bool key_lshift; static bool key_rshift; static int last_code; static cbuf_t* key_buf; static void i8042_process_scode(uint8_t scode, unsigned int flags) { // is this a multi code sequence? bool multi = (last_code == 0xe0); // update the last received code last_code = scode; // save the key up event bit bool key_up = !!(scode & 0x80); scode &= 0x7f; // translate the key based on our translation table uint8_t key_code; if (multi) { key_code = pc_keymap_set1_e0[scode]; } else if (key_lshift || key_rshift) { key_code = pc_keymap_set1_upper[scode]; } else { key_code = pc_keymap_set1_lower[scode]; } LTRACEF("scancode 0x%x, keyup %d, multi %d: keycode 0x%x\n", scode, !!key_up, multi, key_code); // generate a character string to feed into the queue char str[4] = {0}; switch (key_code) { // for all the usual ascii strings, generate the target string directly case 1 ... 0x7f: str[0] = key_code; break; // a few special keys we can generate stuff for directly case KEY_RETURN: case KEY_PAD_ENTER: str[0] = '\n'; break; case KEY_BACKSPACE: str[0] = '\b'; break; case KEY_TAB: str[0] = '\t'; break; // generate vt100 key codes for arrows case KEY_ARROW_UP: str[0] = 0x1b; str[1] = '['; str[2] = 65; break; case KEY_ARROW_DOWN: str[0] = 0x1b; str[1] = '['; str[2] = 66; break; case KEY_ARROW_RIGHT: str[0] = 0x1b; str[1] = '['; str[2] = 67; break; case KEY_ARROW_LEFT: str[0] = 0x1b; str[1] = '['; str[2] = 68; break; // left and right shift are special case KEY_LSHIFT: key_lshift = !key_up; break; case KEY_RSHIFT: key_rshift = !key_up; break; // everything else we just eat default:; // nothing } if (!key_up) { for (uint i = 0; str[i] != '\0'; i++) { LTRACEF("char 0x%hhx (%c)\n", str[i], isprint(str[i]) ? (str[i]) : ' '); cbuf_write_char(key_buf, str[i]); } } } static int i8042_wait_read(void) { int i = 0; while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) { spin(10); i++; } return -(i == I8042_CTL_TIMEOUT); } static int i8042_wait_write(void) { int i = 0; while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) { spin(10); i++; } return -(i == I8042_CTL_TIMEOUT); } static int i8042_flush(void) { unsigned char data __UNUSED; int i = 0; while ((i8042_read_status() & I8042_STR_OBF) && (i++ < I8042_BUFFER_LENGTH)) { spin(10); data = i8042_read_data(); } return i; } static int i8042_command(uint8_t* param, uint16_t command) { int retval = 0, i = 0; retval = i8042_wait_write(); if (!retval) { i8042_write_command(static_cast(command)); } if (!retval) { for (i = 0; i < ((command >> 12) & 0xf); i++) { if ((retval = i8042_wait_write())) { break; } i8042_write_data(param[i]); } } if (!retval) { for (i = 0; i < ((command >> 8) & 0xf); i++) { if ((retval = i8042_wait_read())) { break; } if (i8042_read_status() & I8042_STR_AUXDATA) { param[i] = static_cast(~i8042_read_data()); } else { param[i] = i8042_read_data(); } } } return retval; } static int keyboard_command(uint8_t* param, int command) { int retval = 0, i = 0; retval = i8042_wait_write(); if (!retval) { i8042_write_data(static_cast(command)); } if (!retval) { for (i = 0; i < ((command >> 12) & 0xf); i++) { if ((retval = i8042_wait_write())) { break; } i8042_write_data(param[i]); } } if (!retval) { for (i = 0; i < ((command >> 8) & 0xf); i++) { if ((retval = i8042_wait_read())) { break; } if (i8042_read_status() & I8042_STR_AUXDATA) { param[i] = static_cast(~i8042_read_data()); } else { param[i] = i8042_read_data(); } } } return retval; } static interrupt_eoi i8042_interrupt(void* arg) { // keep handling status on the keyboard controller until no bits are set we care about bool retry; do { retry = false; uint8_t str = i8042_read_status(); // check for incoming data from the controller if (str & I8042_STR_OBF) { uint8_t data = i8042_read_data(); i8042_process_scode(data, ((str & I8042_STR_PARITY) ? I8042_STR_PARITY : 0) | ((str & I8042_STR_TIMEOUT) ? I8042_STR_TIMEOUT : 0)); retry = true; } // TODO: check other status bits here } while (retry); return IRQ_EOI_DEACTIVATE; } int platform_read_key(char* c) { ssize_t len = cbuf_read_char(key_buf, c, true); return static_cast(len); } void platform_init_keyboard(cbuf_t* buffer) { uint8_t ctr; key_buf = buffer; i8042_flush(); if (i8042_command(&ctr, I8042_CMD_CTL_RCTR)) { dprintf(SPEW, "Failed to read CTR while initializing i8042\n"); return; } // turn on translation ctr |= I8042_CTR_XLATE; // enable keyboard and keyboard irq ctr &= static_cast(~I8042_CTR_KBDDIS); ctr |= I8042_CTR_KBDINT; if (i8042_command(&ctr, I8042_CMD_CTL_WCTR)) { dprintf(SPEW, "Failed to write CTR while initializing i8042\n"); return; } /* enable PS/2 port */ i8042_command(NULL, I8042_CMD_KBD_EN); /* send a enable scan command to the keyboard */ keyboard_command(&ctr, 0x1f4); uint32_t irq = apic_io_isa_to_global(ISA_IRQ_KEYBOARD); zx_status_t status = register_int_handler(irq, &i8042_interrupt, NULL); DEBUG_ASSERT(status == ZX_OK); unmask_interrupt(irq); i8042_interrupt(NULL); } void pc_keyboard_reboot(void) { if (i8042_wait_write() != 0) { return; } i8042_write_command(I8042_CMD_PULSE_RESET); // Wait a second for the command to process before declaring failure spin(1000000); }