/* Copyright 2013 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * 8042 keyboard protocol */ #include "atkbd_protocol.h" #include "builtin/assert.h" #include "button.h" #include "chipset.h" #include "common.h" #include "console.h" #include "device_event.h" #include "hooks.h" #include "host_command.h" #include "i8042_protocol.h" #include "keyboard_8042_sharedlib.h" #include "keyboard_config.h" #include "keyboard_protocol.h" #include "lightbar.h" #include "lpc.h" #include "power_button.h" #include "queue.h" #include "shared_mem.h" #include "system.h" #include "task.h" #include "timer.h" #include "util.h" /* Console output macros */ #define CPUTS(outstr) cputs(CC_KEYBOARD, outstr) #define CPRINTS(format, args...) cprints(CC_KEYBOARD, format, ##args) #ifdef CONFIG_KEYBOARD_DEBUG #define CPUTS5(outstr) cputs(CC_KEYBOARD, outstr) #define CPRINTS5(format, args...) cprints(CC_KEYBOARD, format, ##args) #else #define CPUTS5(outstr) #define CPRINTS5(format, args...) #endif /* * This command needs malloc to work. Could we use this instead? * * #define CMD_KEYBOARD_LOG IS_ENABLED(CONFIG_MALLOC) */ #ifdef CONFIG_MALLOC #define CMD_KEYBOARD_LOG 1 #else #define CMD_KEYBOARD_LOG 0 #endif static enum { STATE_ATKBD_CMD = 0, STATE_ATKBD_SCANCODE, STATE_ATKBD_SETLEDS, STATE_ATKBD_EX_SETLEDS_1, /* Expect 2-byte parameter */ STATE_ATKBD_EX_SETLEDS_2, STATE_8042_WRITE_CMD_BYTE, STATE_8042_WRITE_OUTPUT_PORT, STATE_8042_ECHO_MOUSE, STATE_ATKBD_SETREP, STATE_8042_SEND_TO_MOUSE, } data_port_state = STATE_ATKBD_CMD; enum scancode_set_list { SCANCODE_GET_SET = 0, SCANCODE_SET_1, SCANCODE_SET_2, SCANCODE_SET_3, SCANCODE_MAX = SCANCODE_SET_3, }; #define MAX_SCAN_CODE_LEN 4 /* Number of bytes host can get behind before we start generating extra IRQs */ #define KB_TO_HOST_RETRIES 3 /* * Timeout for SETLEDS command. Kernel is supposed to send the second byte * within this period. When timeout occurs, the second byte is received as * 'Unsupported AT keyboard command 0x00' (or 0x04). You can evaluate your * timeout is too long or too short by calculating the duration between 'KB * SETLEDS' and 'Unsupported AT...'. */ #define SETLEDS_TIMEOUT (30 * MSEC) /* * Mutex to control write access to the to-host buffer head. Don't need to * mutex the tail because reads are only done in one place. */ K_MUTEX_DEFINE(to_host_mutex); /* Queue command/data to the host */ enum { CHAN_KBD = 0, CHAN_AUX, CHAN_CMD, }; struct data_byte { uint8_t chan; uint8_t byte; }; static struct queue const to_host = QUEUE_NULL(16, struct data_byte); static struct queue const to_host_cmd = QUEUE_NULL(16, struct data_byte); /* Queue command/data from the host */ enum { HOST_COMMAND = 0, HOST_DATA, }; struct host_byte { uint8_t type; uint8_t byte; }; /* * The buffer for i8042 command from host. So far the largest command * we see from kernel is: * * d1 -> i8042 (command) # enable A20 in i8042_platform_init() of * df -> i8042 (parameter) # serio/i8042-x86ia64io.h file. * ff -> i8042 (command) * 20 -> i8042 (command) # read CTR * * Hence, 5 (actually 4 plus one spare) is large enough, but use 8 for safety. */ static struct queue const from_host = QUEUE_NULL(8, struct host_byte); /* Queue aux data to the host from interrupt context. */ static struct queue const aux_to_host_queue = QUEUE_NULL(16, uint8_t); static int i8042_keyboard_irq_enabled; static int i8042_aux_irq_enabled; /* i8042 global settings */ static int keyboard_enabled; /* default the keyboard is disabled. */ static int aux_chan_enabled; /* default the mouse is disabled. */ static int keystroke_enabled; /* output keystrokes */ static uint8_t resend_command[MAX_SCAN_CODE_LEN]; static uint8_t resend_command_len; static uint8_t controller_ram_address; static uint8_t controller_ram[0x20] = { /* the so called "command byte" */ I8042_XLATE | I8042_AUX_DIS | I8042_KBD_DIS, /* 0x01 - 0x1f are controller RAM */ }; static uint8_t A20_status; /* * Scancode settings */ static enum scancode_set_list scancode_set = SCANCODE_SET_2; /* * Typematic delay, rate and counter variables. * * 7 6 5 4 3 2 1 0 * +-----+-----+-----+-----+-----+-----+-----+-----+ * |un- | delay | B | D | * | used| 0 1 | 0 1 | 0 1 1 | * +-----+-----+-----+-----+-----+-----+-----+-----+ * Formula: * the inter-char delay = (2 ** B) * (D + 8) / 240 (sec) * Default: 500ms delay, 10.9 chars/sec. */ #define DEFAULT_TYPEMATIC_VALUE (BIT(5) | BIT(3) | (3 << 0)) static uint8_t typematic_value_from_host; static int typematic_first_delay; static int typematic_inter_delay; static int typematic_len; /* length of typematic_scan_code */ static uint8_t typematic_scan_code[MAX_SCAN_CODE_LEN]; static timestamp_t typematic_deadline; static timestamp_t setleds_deadline; #define KB_SYSJUMP_TAG 0x4b42 /* "KB" */ #define KB_HOOK_VERSION 2 /* the previous keyboard state before reboot_ec. */ struct kb_state { uint8_t codeset; uint8_t ctlram; uint8_t keystroke_enabled; }; /*****************************************************************************/ /* Keyboard event log */ /* Log the traffic between EC and host -- for debug only */ #define MAX_KBLOG 512 /* Max events in keyboard log */ struct kblog_t { /* * Type: * * a = aux byte enqueued to send to host * c = command byte from host * d = data byte from host * r = typematic * s = byte enqueued to send to host * t = to-host queue tail pointer before type='s' bytes enqueued * u = byte enqueued to send to host with priority * x = to_host queue was cleared * A = byte actually sent to host via LPC as AUX * K = byte actually sent to host via LPC * * The to-host head and tail pointers are logged pre-wrapping to the * queue size. This means that they continually increment as units * are dequeued and enqueued respectively. Since only the bottom * byte of the value is logged they will wrap every 256 units. */ uint8_t type; uint8_t byte; }; static struct kblog_t *kblog_buf; /* Log buffer; NULL if not logging */ static int kblog_len; /* Current log length */ /** * Add event to keyboard log. */ static void kblog_put(char type, uint8_t byte) { if (kblog_buf && kblog_len < MAX_KBLOG) { kblog_buf[kblog_len].type = type; kblog_buf[kblog_len].byte = byte; kblog_len++; } } /*****************************************************************************/ void keyboard_host_write(int data, int is_cmd) { struct host_byte h; h.type = is_cmd ? HOST_COMMAND : HOST_DATA; h.byte = data; queue_add_unit(&from_host, &h); task_wake(TASK_ID_KEYPROTO); } /** * Enable keyboard IRQ generation. * * @param enable Enable (!=0) or disable (0) IRQ generation. */ static void keyboard_enable_irq(int enable) { CPRINTS("KB IRQ %s", enable ? "enable" : "disable"); i8042_keyboard_irq_enabled = enable; if (enable) lpc_keyboard_resume_irq(); } /** * Enable mouse IRQ generation. * * @param enable Enable (!=0) or disable (0) IRQ generation. */ static void aux_enable_irq(int enable) { CPRINTS("AUX IRQ %s", enable ? "enable" : "disable"); i8042_aux_irq_enabled = enable; } /** * Send a scan code to the host. * * The EC lib will push the scan code bytes to host via port 0x60 and assert * the IBF flag to trigger an interrupt. The EC lib must queue them if the * host cannot read the previous byte away in time. * * @param len Number of bytes to send to the host * @param bytes Data to send * @param chan Channel to send data on */ static void i8042_send_to_host(int len, const uint8_t *bytes, uint8_t chan, int is_typematic) { int i; struct data_byte data; /* Enqueue output data if there's space */ mutex_lock(&to_host_mutex); if (is_typematic && !typematic_len) { for (i = 0; i < len; i++) kblog_put('r', bytes[i]); } else { struct queue const *queue = &to_host; if (chan == CHAN_CMD) queue = &to_host_cmd; for (i = 0; i < len; i++) { char type; if (chan == CHAN_AUX) type = 'a'; else if (chan == CHAN_CMD) type = 'u'; else type = 's'; kblog_put(type, bytes[i]); } if (queue_space(queue) >= len) { kblog_put('t', queue->state->tail); for (i = 0; i < len; i++) { data.chan = chan; data.byte = bytes[i]; queue_add_unit(queue, &data); } } } mutex_unlock(&to_host_mutex); /* Wake up the task to move from queue to host */ task_wake(TASK_ID_KEYPROTO); } /* Change to set 1 if the I8042_XLATE flag is set. */ static enum scancode_set_list acting_code_set(enum scancode_set_list set) { /* Always generate set 1 if keyboard translation is enabled */ if (controller_ram[0] & I8042_XLATE) return SCANCODE_SET_1; return set; } static int is_supported_code_set(enum scancode_set_list set) { return (set == SCANCODE_SET_1 || set == SCANCODE_SET_2); } /** * Return the make or break code bytes for the active scancode set. * * @param make_code The make code to generate the make or break code from * @param pressed Whether the key or button was pressed * @param code_set The scancode set being used * @param scan_code An array of bytes to store the make or break code in * @param len The number of valid bytes to send in scan_code */ static void scancode_bytes(uint16_t make_code, int8_t pressed, enum scancode_set_list code_set, uint8_t *scan_code, int32_t *len) { *len = 0; /* Output the make code (from table) */ if (make_code >= 0x0100) { scan_code[(*len)++] = make_code >> 8; make_code &= 0xff; } switch (code_set) { case SCANCODE_SET_1: make_code = scancode_translate_set2_to_1(make_code); scan_code[(*len)++] = pressed ? make_code : (make_code | 0x80); break; case SCANCODE_SET_2: if (pressed) { scan_code[(*len)++] = make_code; } else { scan_code[(*len)++] = 0xf0; scan_code[(*len)++] = make_code; } break; default: break; } } static enum ec_error_list matrix_callback(int8_t row, int8_t col, int8_t pressed, enum scancode_set_list code_set, uint8_t *scan_code, int32_t *len) { uint16_t make_code; ASSERT(scan_code); ASSERT(len); if (row >= KEYBOARD_ROWS || col >= keyboard_cols) return EC_ERROR_INVAL; make_code = get_scancode_set2(row, col); #ifdef CONFIG_KEYBOARD_SCANCODE_CALLBACK { enum ec_error_list r = keyboard_scancode_callback(&make_code, pressed); if (r != EC_SUCCESS) return r; } #endif code_set = acting_code_set(code_set); if (!is_supported_code_set(code_set)) { CPRINTS("KB scancode set %d unsupported", code_set); return EC_ERROR_UNIMPLEMENTED; } if (!make_code) { CPRINTS("KB scancode %d:%d missing", row, col); return EC_ERROR_UNIMPLEMENTED; } scancode_bytes(make_code, pressed, code_set, scan_code, len); return EC_SUCCESS; } /** * Set typematic delays based on host data byte. */ static void set_typematic_delays(uint8_t data) { typematic_value_from_host = data; typematic_first_delay = MSEC * (((typematic_value_from_host & 0x60) >> 5) + 1) * 250; typematic_inter_delay = SECOND * (1 << ((typematic_value_from_host & 0x18) >> 3)) * ((typematic_value_from_host & 0x7) + 8) / 240; } test_export_static void reset_rate_and_delay(void) { set_typematic_delays(DEFAULT_TYPEMATIC_VALUE); } void keyboard_clear_buffer(void) { CPRINTS("KB Clear Buffer"); mutex_lock(&to_host_mutex); kblog_put('x', queue_count(&to_host)); queue_init(&to_host); queue_init(&to_host_cmd); mutex_unlock(&to_host_mutex); lpc_keyboard_clear_buffer(); } static void keyboard_wakeup(void) { host_set_single_event(EC_HOST_EVENT_KEY_PRESSED); } test_export_static void set_typematic_key(const uint8_t *scan_code, int32_t len) { typematic_deadline.val = get_time().val + typematic_first_delay; memcpy(typematic_scan_code, scan_code, len); typematic_len = len; } void clear_typematic_key(void) { typematic_len = 0; } void keyboard_state_changed(int row, int col, int is_pressed) { uint8_t scan_code[MAX_SCAN_CODE_LEN]; int32_t len = 0; enum ec_error_list ret; #ifdef CONFIG_KEYBOARD_DEBUG uint8_t mylabel = get_keycap_label(row, col); if (mylabel & KEYCAP_LONG_LABEL_BIT) CPRINTS("KB (%d,%d)=%d %s", row, col, is_pressed, get_keycap_long_label(mylabel & KEYCAP_LONG_LABEL_INDEX_BITMASK)); else CPRINTS("KB (%d,%d)=%d %c", row, col, is_pressed, mylabel); #endif ret = matrix_callback(row, col, is_pressed, scancode_set, scan_code, &len); if (ret == EC_SUCCESS) { ASSERT(len > 0); if (keystroke_enabled) i8042_send_to_host(len, scan_code, CHAN_KBD, 0); } if (is_pressed) { keyboard_wakeup(); set_typematic_key(scan_code, len); task_wake(TASK_ID_KEYPROTO); } else { clear_typematic_key(); } } static void keystroke_enable(int enable) { if (!keystroke_enabled && enable) CPRINTS("KS enable"); else if (keystroke_enabled && !enable) CPRINTS("KS disable"); keystroke_enabled = enable; } static void keyboard_enable(int enable) { if (!keyboard_enabled && enable) CPRINTS("KB enable"); else if (keyboard_enabled && !enable) CPRINTS("KB disable"); keyboard_enabled = enable; } static void aux_enable(int enable) { if (!aux_chan_enabled && enable) CPRINTS("AUX enabled"); else if (aux_chan_enabled && !enable) CPRINTS("AUX disabled"); aux_chan_enabled = enable; } static uint8_t read_ctl_ram(uint8_t addr) { if (addr < ARRAY_SIZE(controller_ram)) return controller_ram[addr]; else return 0; } /** * Manipulate the controller_ram[]. * * Some bits change may trigger internal state change. */ static void update_ctl_ram(uint8_t addr, uint8_t data) { uint8_t orig; if (addr >= ARRAY_SIZE(controller_ram)) return; orig = controller_ram[addr]; controller_ram[addr] = data; CPRINTS5("KB set CTR_RAM(0x%02x)=0x%02x (old:0x%02x)", addr, data, orig); if (addr == 0x00) { /* Keyboard enable/disable */ /* Enable IRQ before enable keyboard (queue chars to host) */ if (!(orig & I8042_ENIRQ1) && (data & I8042_ENIRQ1)) keyboard_enable_irq(1); if (!(orig & I8042_ENIRQ12) && (data & I8042_ENIRQ12)) aux_enable_irq(1); /* Handle the I8042_KBD_DIS bit */ keyboard_enable(!(data & I8042_KBD_DIS)); /* Handle the I8042_AUX_DIS bit */ aux_enable(!(data & I8042_AUX_DIS)); /* * Disable IRQ after disable keyboard so that every char must * have informed the host. */ if ((orig & I8042_ENIRQ1) && !(data & I8042_ENIRQ1)) keyboard_enable_irq(0); if ((orig & I8042_ENIRQ12) && !(data & I8042_ENIRQ12)) aux_enable_irq(0); } } /** * Handle the port 0x60 writes from host. * * Returns 1 if the event was handled. */ static int handle_mouse_data(uint8_t data, uint8_t *output, int *count) { int out_len = 0; switch (data_port_state) { case STATE_8042_ECHO_MOUSE: CPRINTS5("STATE_8042_ECHO_MOUSE: 0x%02x", data); output[out_len++] = data; data_port_state = STATE_ATKBD_CMD; break; case STATE_8042_SEND_TO_MOUSE: CPRINTS5("STATE_8042_SEND_TO_MOUSE: 0x%02x", data); send_aux_data_to_device(data); data_port_state = STATE_ATKBD_CMD; break; default: /* STATE_ATKBD_CMD */ return 0; } ASSERT(out_len <= MAX_SCAN_CODE_LEN); *count = out_len; return 1; } /** * Handle the port 0x60 writes from host. * * This functions returns the number of bytes stored in *output buffer. */ static int handle_keyboard_data(uint8_t data, uint8_t *output) { int out_len = 0; int save_for_resend = 1; int i; switch (data_port_state) { case STATE_ATKBD_SCANCODE: CPRINTS5("KB eaten by STATE_ATKBD_SCANCODE: 0x%02x", data); if (data == SCANCODE_GET_SET) { output[out_len++] = ATKBD_RET_ACK; output[out_len++] = scancode_set; } else { scancode_set = data; CPRINTS("KB scancode set to %d", scancode_set); output[out_len++] = ATKBD_RET_ACK; } data_port_state = STATE_ATKBD_CMD; break; case STATE_ATKBD_SETLEDS: CPRINTS5("KB eaten by STATE_ATKBD_SETLEDS"); output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_CMD; break; case STATE_ATKBD_EX_SETLEDS_1: CPRINTS5("KB eaten by STATE_ATKBD_EX_SETLEDS_1"); output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_EX_SETLEDS_2; break; case STATE_ATKBD_EX_SETLEDS_2: CPRINTS5("KB eaten by STATE_ATKBD_EX_SETLEDS_2"); output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_CMD; break; case STATE_8042_WRITE_CMD_BYTE: CPRINTS5("KB eaten by STATE_8042_WRITE_CMD_BYTE: 0x%02x", data); update_ctl_ram(controller_ram_address, data); data_port_state = STATE_ATKBD_CMD; break; case STATE_8042_WRITE_OUTPUT_PORT: CPRINTS5("KB eaten by STATE_8042_WRITE_OUTPUT_PORT: 0x%02x", data); A20_status = (data & BIT(1)) ? 1 : 0; data_port_state = STATE_ATKBD_CMD; break; case STATE_ATKBD_SETREP: CPRINTS5("KB eaten by STATE_ATKBD_SETREP: 0x%02x", data); set_typematic_delays(data); output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_CMD; break; default: /* STATE_ATKBD_CMD */ switch (data) { case ATKBD_CMD_GSCANSET: /* also ATKBD_CMD_SSCANSET */ output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_SCANCODE; break; case ATKBD_CMD_SETLEDS: /* Chrome OS doesn't have keyboard LEDs, so ignore */ output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_SETLEDS; setleds_deadline.val = get_time().val + SETLEDS_TIMEOUT; CPRINTS5("KB SETLEDS"); break; case ATKBD_CMD_EX_SETLEDS: output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_EX_SETLEDS_1; break; case ATKBD_CMD_DIAG_ECHO: output[out_len++] = ATKBD_RET_ACK; output[out_len++] = ATKBD_RET_ECHO; break; case ATKBD_CMD_GETID: /* fall-thru */ case ATKBD_CMD_OK_GETID: output[out_len++] = ATKBD_RET_ACK; output[out_len++] = 0xab; /* Regular keyboards */ output[out_len++] = 0x83; break; case ATKBD_CMD_SETREP: output[out_len++] = ATKBD_RET_ACK; data_port_state = STATE_ATKBD_SETREP; break; case ATKBD_CMD_ENABLE: output[out_len++] = ATKBD_RET_ACK; keystroke_enable(1); keyboard_clear_buffer(); break; case ATKBD_CMD_RESET_DIS: output[out_len++] = ATKBD_RET_ACK; keystroke_enable(0); reset_rate_and_delay(); keyboard_clear_buffer(); break; case ATKBD_CMD_RESET_DEF: output[out_len++] = ATKBD_RET_ACK; reset_rate_and_delay(); keyboard_clear_buffer(); break; case ATKBD_CMD_RESET: reset_rate_and_delay(); keyboard_clear_buffer(); output[out_len++] = ATKBD_RET_ACK; output[out_len++] = ATKBD_RET_TEST_SUCCESS; break; case ATKBD_CMD_RESEND: save_for_resend = 0; for (i = 0; i < resend_command_len; ++i) output[out_len++] = resend_command[i]; break; case 0x60: /* fall-thru */ case 0x45: /* U-boot hack. Just ignore; don't reply. */ break; case ATKBD_CMD_SETALL_MB: /* fall-thru */ case ATKBD_CMD_SETALL_MBR: case ATKBD_CMD_EX_ENABLE: default: output[out_len++] = ATKBD_RET_RESEND; CPRINTS("KB Unsupported AT keyboard command 0x%02x", data); break; } } /* For resend, keep output before leaving. */ if (out_len && save_for_resend) { ASSERT(out_len <= MAX_SCAN_CODE_LEN); for (i = 0; i < out_len; ++i) resend_command[i] = output[i]; resend_command_len = out_len; } ASSERT(out_len <= MAX_SCAN_CODE_LEN); return out_len; } /** * Handle the port 0x64 writes from host. * * This functions returns the number of bytes stored in *output buffer. * BUT those bytes will appear at port 0x60. */ static int handle_keyboard_command(uint8_t command, uint8_t *output) { int out_len = 0; CPRINTS5("KB recv cmd: 0x%02x", command); kblog_put('c', command); switch (command) { case I8042_READ_CMD_BYTE: /* * Ensure that the keyboard buffer is cleared before adding * command byte to it. Since the host is asking for command * byte, sending it buffered key press data can confuse the * host and result in it taking incorrect action. */ keyboard_clear_buffer(); output[out_len++] = read_ctl_ram(0); break; case I8042_WRITE_CMD_BYTE: data_port_state = STATE_8042_WRITE_CMD_BYTE; controller_ram_address = command - 0x60; break; case I8042_DIS_KB: update_ctl_ram(0, read_ctl_ram(0) | I8042_KBD_DIS); reset_rate_and_delay(); typematic_len = 0; /* stop typematic */ keyboard_clear_buffer(); break; case I8042_ENA_KB: update_ctl_ram(0, read_ctl_ram(0) & ~I8042_KBD_DIS); keystroke_enable(1); keyboard_clear_buffer(); break; case I8042_READ_OUTPUT_PORT: output[out_len++] = (lpc_keyboard_input_pending() ? BIT(5) : 0) | (lpc_keyboard_has_char() ? BIT(4) : 0) | (A20_status ? BIT(1) : 0) | 1; /* Main processor in normal mode */ break; case I8042_WRITE_OUTPUT_PORT: data_port_state = STATE_8042_WRITE_OUTPUT_PORT; break; case I8042_RESET_SELF_TEST: output[out_len++] = 0x55; /* Self test success */ break; case I8042_TEST_KB_PORT: output[out_len++] = 0x00; break; case I8042_DIS_MOUSE: update_ctl_ram(0, read_ctl_ram(0) | I8042_AUX_DIS); break; case I8042_ENA_MOUSE: update_ctl_ram(0, read_ctl_ram(0) & ~I8042_AUX_DIS); break; case I8042_TEST_MOUSE: output[out_len++] = 0; /* No error detected */ break; case I8042_ECHO_MOUSE: data_port_state = STATE_8042_ECHO_MOUSE; break; case I8042_SEND_TO_MOUSE: data_port_state = STATE_8042_SEND_TO_MOUSE; break; case I8042_SYSTEM_RESET: chipset_reset(CHIPSET_RESET_KB_SYSRESET); break; default: if (command >= I8042_READ_CTL_RAM && command <= I8042_READ_CTL_RAM_END) { output[out_len++] = read_ctl_ram(command - 0x20); } else if (command >= I8042_WRITE_CTL_RAM && command <= I8042_WRITE_CTL_RAM_END) { data_port_state = STATE_8042_WRITE_CMD_BYTE; controller_ram_address = command - 0x60; } else if (command == I8042_DISABLE_A20) { A20_status = 0; } else if (command == I8042_ENABLE_A20) { A20_status = 1; } else if (command >= I8042_PULSE_START && command <= I8042_PULSE_END) { /* Pulse Output Bits, * b0=0 to reset CPU, see I8042_SYSTEM_RESET above * b1=0 to disable A20 line */ A20_status = command & BIT(1) ? 1 : 0; } else { CPRINTS("KB unsupported cmd: 0x%02x", command); reset_rate_and_delay(); keyboard_clear_buffer(); output[out_len++] = I8042_RET_NAK; data_port_state = STATE_ATKBD_CMD; } break; } return out_len; } static void i8042_handle_from_host(void) { struct host_byte h; int ret_len; uint8_t output[MAX_SCAN_CODE_LEN]; uint8_t chan; while (queue_remove_unit(&from_host, &h)) { if (h.type == HOST_COMMAND) { ret_len = handle_keyboard_command(h.byte, output); chan = CHAN_KBD; } else { CPRINTS5("KB recv data: 0x%02x", h.byte); kblog_put('d', h.byte); if (IS_ENABLED(CONFIG_8042_AUX) && handle_mouse_data(h.byte, output, &ret_len)) { chan = CHAN_AUX; } else { ret_len = handle_keyboard_data(h.byte, output); chan = CHAN_CMD; } } i8042_send_to_host(ret_len, output, chan, 0); } } void keyboard_protocol_task(void *u) { int wait = -1; int retries = 0; reset_rate_and_delay(); while (1) { /* Wait for next host read/write */ task_wait_event(wait); while (1) { timestamp_t t = get_time(); struct data_byte entry; /* Handle typematic */ if (!typematic_len) { /* Typematic disabled; wait for enable */ wait = -1; } else if (timestamp_expired(typematic_deadline, &t)) { /* Ready for next typematic keystroke */ if (keystroke_enabled) i8042_send_to_host(typematic_len, typematic_scan_code, CHAN_KBD, 1); typematic_deadline.val = t.val + typematic_inter_delay; wait = typematic_inter_delay; } else { /* Wait for remaining interval */ wait = typematic_deadline.val - t.val; } /* Handle command/data write from host */ i8042_handle_from_host(); /* Check if we have data to send to host */ if (queue_is_empty(&to_host) && queue_is_empty(&to_host_cmd)) break; /* * Check if the output buffer is full. We can't proceed * until the host read the data. */ if (lpc_keyboard_has_char()) { /* If interrupts disabled, nothing we can do */ if (!i8042_keyboard_irq_enabled && !i8042_aux_irq_enabled) break; /* Give the host a little longer to respond */ if (++retries < KB_TO_HOST_RETRIES) break; /* * We keep getting data, but the host keeps * ignoring us. Fine, we're done waiting. * Hey, host, are you ever gonna get to this * data? Send it another interrupt in case it * somehow missed the first one. */ CPRINTS("KB host not responding"); lpc_keyboard_resume_irq(); retries = 0; break; } /* * We know DBBOUT is empty but we need act quickly as * the host might be sending a byte to DBBIN. * * So be cautious if you're adding any code below up to * lpc_keyboard_put_char since that'll increase the race * condition. For example, you don't want to add CPRINTS * or kblog_put. * * We should claim OBF=1 atomically to prevent the host * from writing to DBBIN (i.e. set-ibf-if-not-obf). It's * not possible for NPCX because NPCX's HIKMST-IBF is * read-only. */ /* Get a char from buffer. */ if (queue_count(&to_host_cmd)) { queue_remove_unit(&to_host_cmd, &entry); } else if (data_port_state == STATE_ATKBD_SETLEDS) { /* to_host_cmd is empty but in SETLEDS */ if (!timestamp_expired(setleds_deadline, &t)) /* Let's wait for the 2nd byte. */ break; /* Didn't receive 2nd byte. Go back to CMD. */ CPRINTS("KB SETLEDS timeout"); data_port_state = STATE_ATKBD_CMD; } else { /* to_host isn't empty && not in SETLEDS */ queue_remove_unit(&to_host, &entry); } /* Write to host. */ if (entry.chan == CHAN_AUX && IS_ENABLED(CONFIG_8042_AUX)) { lpc_aux_put_char(entry.byte, i8042_aux_irq_enabled); kblog_put('A', entry.byte); } else { lpc_keyboard_put_char( entry.byte, i8042_keyboard_irq_enabled); kblog_put('K', entry.byte); } retries = 0; } } } static void send_aux_data_to_host_deferred(void) { uint8_t data; if (IS_ENABLED(CONFIG_DEVICE_EVENT) && chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) device_set_single_event(EC_DEVICE_EVENT_TRACKPAD); while (!queue_is_empty(&aux_to_host_queue)) { queue_remove_unit(&aux_to_host_queue, &data); if (aux_chan_enabled && IS_ENABLED(CONFIG_8042_AUX)) i8042_send_to_host(1, &data, CHAN_AUX, 0); else CPRINTS("AUX Callback ignored"); } } DECLARE_DEFERRED(send_aux_data_to_host_deferred); /** * Send aux data to host from interrupt context. * * @param data Aux response to send to host. */ void send_aux_data_to_host_interrupt(uint8_t data) { queue_add_unit(&aux_to_host_queue, &data); hook_call_deferred(&send_aux_data_to_host_deferred_data, 0); } /** * Handle button changing state. * * @param button Type of button that changed * @param is_pressed Whether the button was pressed or released */ test_mockable void keyboard_update_button(enum keyboard_button_type button, int is_pressed) { uint8_t scan_code[MAX_SCAN_CODE_LEN]; uint32_t len; struct button_8042_t button_8042; enum scancode_set_list code_set; /* * Only send the scan code if main chipset is fully awake and * keystrokes are enabled. */ if (!chipset_in_state(CHIPSET_STATE_ON) || !keystroke_enabled) return; code_set = acting_code_set(scancode_set); if (!is_supported_code_set(code_set)) return; button_8042 = buttons_8042[button]; scancode_bytes(button_8042.scancode, is_pressed, code_set, scan_code, &len); ASSERT(len > 0); if (button_8042.repeat) { if (is_pressed) set_typematic_key(scan_code, len); else clear_typematic_key(); } if (keystroke_enabled) { CPRINTS5("KB UPDATE BTN"); i8042_send_to_host(len, scan_code, CHAN_KBD, 0); task_wake(TASK_ID_KEYPROTO); } } /*****************************************************************************/ /* Console commands */ #ifdef CONFIG_CMD_KEYBOARD static int command_typematic(int argc, const char **argv) { int i; if (argc == 3) { typematic_first_delay = strtoi(argv[1], NULL, 0) * MSEC; typematic_inter_delay = strtoi(argv[2], NULL, 0) * MSEC; } ccprintf("From host: 0x%02x\n", typematic_value_from_host); ccprintf("First delay: %3d ms\n", typematic_first_delay / 1000); ccprintf("Inter delay: %3d ms\n", typematic_inter_delay / 1000); ccprintf("Now: %.6" PRId64 "\n", get_time().val); ccprintf("Deadline: %.6" PRId64 "\n", typematic_deadline.val); ccputs("Repeat scan code: {"); for (i = 0; i < typematic_len; ++i) ccprintf("0x%02x, ", typematic_scan_code[i]); ccputs("}\n"); return EC_SUCCESS; } static int command_codeset(int argc, const char **argv) { if (argc == 2) { int set = strtoi(argv[1], NULL, 0); switch (set) { case SCANCODE_SET_1: /* fall-thru */ case SCANCODE_SET_2: /* fall-thru */ scancode_set = set; break; default: return EC_ERROR_PARAM1; } } ccprintf("Set: %d\n", scancode_set); ccprintf("I8042_XLATE: %d\n", controller_ram[0] & I8042_XLATE ? 1 : 0); return EC_SUCCESS; } static int command_controller_ram(int argc, const char **argv) { int index; if (argc < 2) return EC_ERROR_PARAM_COUNT; index = strtoi(argv[1], NULL, 0); if (index >= ARRAY_SIZE(controller_ram)) return EC_ERROR_PARAM1; if (argc >= 3) update_ctl_ram(index, strtoi(argv[2], NULL, 0)); ccprintf("%d = 0x%02x\n", index, controller_ram[index]); return EC_SUCCESS; } static int command_keyboard_log(int argc, const char **argv) { int i; /* If no args, print log */ if (argc == 1) { ccprintf("KBC log (len=%d):\n", kblog_len); for (i = 0; kblog_buf && i < kblog_len; ++i) { ccprintf("%c.%02x ", kblog_buf[i].type, kblog_buf[i].byte); if ((i & 15) == 15) { ccputs("\n"); cflush(); } } ccputs("\n"); return EC_SUCCESS; } /* Otherwise, enable/disable */ if (!parse_bool(argv[1], &i)) return EC_ERROR_PARAM1; if (i) { if (!kblog_buf) { int rv = SHARED_MEM_ACQUIRE_CHECK(sizeof(*kblog_buf) * MAX_KBLOG, (char **)&kblog_buf); if (rv != EC_SUCCESS) kblog_buf = NULL; kblog_len = 0; return rv; } } else { kblog_len = 0; if (kblog_buf) shared_mem_release(kblog_buf); kblog_buf = NULL; } return EC_SUCCESS; } static int command_keyboard(int argc, const char **argv) { int ena; if (argc > 1) { if (!parse_bool(argv[1], &ena)) return EC_ERROR_PARAM1; keyboard_enable(ena); } ccprintf("Enabled: %d\n", keyboard_enabled); return EC_SUCCESS; } static int command_8042_internal(int argc, const char **argv) { int i; ccprintf("data_port_state=%d\n", data_port_state); ccprintf("i8042_keyboard_irq_enabled=%d\n", i8042_keyboard_irq_enabled); ccprintf("i8042_aux_irq_enabled=%d\n", i8042_aux_irq_enabled); ccprintf("keyboard_enabled=%d\n", keyboard_enabled); ccprintf("keystroke_enabled=%d\n", keystroke_enabled); ccprintf("aux_chan_enabled=%d\n", aux_chan_enabled); ccprintf("resend_command[]={"); for (i = 0; i < resend_command_len; i++) ccprintf("0x%02x, ", resend_command[i]); ccprintf("}\n"); ccprintf("controller_ram_address=0x%02x\n", controller_ram_address); ccprintf("A20_status=%d\n", A20_status); ccprintf("from_host[]={"); for (i = 0; i < queue_count(&from_host); ++i) { struct host_byte entry; queue_peek_units(&from_host, &entry, i, 1); ccprintf("0x%02x, 0x%02x, ", entry.type, entry.byte); } ccprintf("}\n"); ccprintf("to_host[]={"); for (i = 0; i < queue_count(&to_host); ++i) { struct data_byte entry; queue_peek_units(&to_host, &entry, i, 1); ccprintf("0x%02x%s, ", entry.byte, entry.chan == CHAN_AUX ? " aux" : ""); } ccprintf("}\n"); return EC_SUCCESS; } /* Zephyr only provides these as subcommands*/ #ifndef CONFIG_ZEPHYR DECLARE_CONSOLE_COMMAND(typematic, command_typematic, "[first] [inter]", "Get/set typematic delays"); DECLARE_CONSOLE_COMMAND(codeset, command_codeset, "[set]", "Get/set keyboard codeset"); DECLARE_CONSOLE_COMMAND(ctrlram, command_controller_ram, "index [value]", "Get/set keyboard controller RAM"); DECLARE_CONSOLE_COMMAND(kblog, command_keyboard_log, "[on | off]", "Print or toggle keyboard event log"); DECLARE_CONSOLE_COMMAND(kbd, command_keyboard, "[on | off]", "Print or toggle keyboard info"); #endif static int command_8042(int argc, const char **argv) { if (argc >= 2) { if (!strcasecmp(argv[1], "internal")) return command_8042_internal(argc, argv); else if (!strcasecmp(argv[1], "typematic")) return command_typematic(argc - 1, argv + 1); else if (!strcasecmp(argv[1], "codeset")) return command_codeset(argc - 1, argv + 1); else if (!strcasecmp(argv[1], "ctrlram")) return command_controller_ram(argc - 1, argv + 1); else if (CMD_KEYBOARD_LOG && !strcasecmp(argv[1], "kblog")) return command_keyboard_log(argc - 1, argv + 1); else if (!strcasecmp(argv[1], "kbd")) return command_keyboard(argc - 1, argv + 1); else return EC_ERROR_PARAM1; } else { const char *ctlram_argv[] = { "ctrlram", "0" }; ccprintf("\n- Typematic:\n"); command_typematic(argc, argv); ccprintf("\n- Codeset:\n"); command_codeset(argc, argv); ccprintf("\n- Control RAM:\n"); command_controller_ram(sizeof(ctlram_argv) / sizeof(ctlram_argv[0]), ctlram_argv); if (CMD_KEYBOARD_LOG) { ccprintf("\n- Keyboard log:\n"); command_keyboard_log(argc, argv); } ccprintf("\n- Keyboard:\n"); command_keyboard(argc, argv); ccprintf("\n- Internal:\n"); command_8042_internal(argc, argv); ccprintf("\n"); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(8042, command_8042, "[internal | typematic | codeset | ctrlram |" " kblog | kbd]", "Print 8042 state in one place"); #endif /*****************************************************************************/ /* Hooks */ /** * Preserve the states of keyboard controller to keep the initialized states * between reboot_ec commands. Saving info include: * * - code set * - controller_ram[0]: * - XLATE * - KB/TP disabled * - KB/TP IRQ enabled */ static void keyboard_preserve_state(void) { struct kb_state state; state.codeset = scancode_set; state.ctlram = controller_ram[0]; state.keystroke_enabled = keystroke_enabled; system_add_jump_tag(KB_SYSJUMP_TAG, KB_HOOK_VERSION, sizeof(state), &state); } DECLARE_HOOK(HOOK_SYSJUMP, keyboard_preserve_state, HOOK_PRIO_DEFAULT); /** * Restore the keyboard states after reboot_ec command. See above function. */ static void keyboard_restore_state(void) { const struct kb_state *prev; int version, size; prev = (const struct kb_state *)system_get_jump_tag(KB_SYSJUMP_TAG, &version, &size); if (prev && version == KB_HOOK_VERSION && size == sizeof(*prev)) { /* Coming back from a sysjump, so restore settings. */ scancode_set = prev->codeset; update_ctl_ram(0, prev->ctlram); keystroke_enabled = prev->keystroke_enabled; } } DECLARE_HOOK(HOOK_INIT, keyboard_restore_state, HOOK_PRIO_DEFAULT); #if defined(CONFIG_POWER_BUTTON) && !defined(CONFIG_MKBP_INPUT_DEVICES) /** * Handle power button changing state. */ static void keyboard_power_button(void) { keyboard_update_button(KEYBOARD_BUTTON_POWER, power_button_is_pressed()); } DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, keyboard_power_button, HOOK_PRIO_DEFAULT); #endif /* CONFIG_POWER_BUTTON && !CONFIG_MKBP_INPUT_DEVICES */ #ifdef TEST_BUILD void test_keyboard_8042_set_resend_command(const uint8_t *data, int length) { length = MIN(length, sizeof(resend_command)); memcpy(resend_command, data, length); resend_command_len = length; } void test_keyboard_8042_reset(void) { /* Initialize controller ram */ memset(controller_ram, 0, sizeof(controller_ram)); controller_ram[0] = I8042_XLATE | I8042_AUX_DIS | I8042_KBD_DIS; /* Typematic state reset */ reset_rate_and_delay(); clear_typematic_key(); /* Use default scancode set # 2 */ scancode_set = SCANCODE_SET_2; /* Keyboard not enabled (matches I8042_KBD_DIS bit being set) */ keyboard_enabled = false; } #endif /* TEST_BUILD */