1185 lines
29 KiB
C
1185 lines
29 KiB
C
/* Copyright 2013 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*
|
|
* 8042 keyboard protocol
|
|
*/
|
|
|
|
#include "chipset.h"
|
|
#include "button.h"
|
|
#include "common.h"
|
|
#include "console.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
|
|
|
|
static enum {
|
|
STATE_NORMAL = 0,
|
|
STATE_SCANCODE,
|
|
STATE_SETLEDS,
|
|
STATE_EX_SETLEDS_1, /* Expect 2-byte parameter */
|
|
STATE_EX_SETLEDS_2,
|
|
STATE_WRITE_CMD_BYTE,
|
|
STATE_WRITE_OUTPUT_PORT,
|
|
STATE_ECHO_MOUSE,
|
|
STATE_SETREP,
|
|
STATE_SEND_TO_MOUSE,
|
|
} data_port_state = STATE_NORMAL;
|
|
|
|
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
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static struct mutex to_host_mutex;
|
|
|
|
static struct queue const to_host = QUEUE_NULL(16, uint8_t);
|
|
|
|
/* 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);
|
|
|
|
static int i8042_irq_enabled;
|
|
|
|
/* i8042 global settings */
|
|
static int keyboard_enabled; /* default the keyboard 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;
|
|
|
|
#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:
|
|
*
|
|
* s = byte enqueued to send to host
|
|
* t = to-host queue tail pointer before type='s' bytes enqueued
|
|
*
|
|
* d = data byte from host
|
|
* c = command byte from host
|
|
*
|
|
* k = to-host queue head pointer before byte dequeued
|
|
* 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_irq_enabled = enable;
|
|
if (enable)
|
|
lpc_keyboard_resume_irq();
|
|
}
|
|
|
|
/**
|
|
* 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 to_host Data to send
|
|
*/
|
|
static void i8042_send_to_host(int len, const uint8_t *bytes)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
kblog_put('s', bytes[i]);
|
|
|
|
/* Enqueue output data if there's space */
|
|
mutex_lock(&to_host_mutex);
|
|
if (queue_space(&to_host) >= len) {
|
|
kblog_put('t', to_host.state->tail);
|
|
queue_add_units(&to_host, bytes, len);
|
|
}
|
|
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 = scancode_set2[col][row];
|
|
|
|
#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;
|
|
}
|
|
|
|
static void reset_rate_and_delay(void)
|
|
{
|
|
set_typematic_delays(DEFAULT_TYPEMATIC_VALUE);
|
|
}
|
|
|
|
void keyboard_clear_buffer(void)
|
|
{
|
|
mutex_lock(&to_host_mutex);
|
|
queue_init(&to_host);
|
|
mutex_unlock(&to_host_mutex);
|
|
lpc_keyboard_clear_buffer();
|
|
}
|
|
|
|
static void keyboard_wakeup(void)
|
|
{
|
|
host_set_single_event(EC_HOST_EVENT_KEY_PRESSED);
|
|
}
|
|
|
|
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
|
|
char mylabel = keycap_label[col][row];
|
|
|
|
if (mylabel & KEYCAP_LONG_LABEL_BIT)
|
|
CPRINTS("KB (%d,%d)=%d %s", row, col, is_pressed,
|
|
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);
|
|
}
|
|
|
|
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 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);
|
|
|
|
/* Handle the I8042_KBD_DIS bit */
|
|
keyboard_enable(!(data & I8042_KBD_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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
CPRINTS5("KB recv data: 0x%02x", data);
|
|
kblog_put('d', data);
|
|
|
|
switch (data_port_state) {
|
|
case STATE_SCANCODE:
|
|
CPRINTS5("KB eaten by STATE_SCANCODE: 0x%02x", data);
|
|
if (data == SCANCODE_GET_SET) {
|
|
output[out_len++] = I8042_RET_ACK;
|
|
output[out_len++] = scancode_set;
|
|
} else {
|
|
scancode_set = data;
|
|
CPRINTS("KB scancode set to %d", scancode_set);
|
|
output[out_len++] = I8042_RET_ACK;
|
|
}
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_SETLEDS:
|
|
CPRINTS5("KB eaten by STATE_SETLEDS");
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_EX_SETLEDS_1:
|
|
CPRINTS5("KB eaten by STATE_EX_SETLEDS_1");
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_EX_SETLEDS_2;
|
|
break;
|
|
|
|
case STATE_EX_SETLEDS_2:
|
|
CPRINTS5("KB eaten by STATE_EX_SETLEDS_2");
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_WRITE_CMD_BYTE:
|
|
CPRINTS5("KB eaten by STATE_WRITE_CMD_BYTE: 0x%02x",
|
|
data);
|
|
update_ctl_ram(controller_ram_address, data);
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_WRITE_OUTPUT_PORT:
|
|
CPRINTS5("KB eaten by STATE_WRITE_OUTPUT_PORT: 0x%02x",
|
|
data);
|
|
A20_status = (data & BIT(1)) ? 1 : 0;
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_ECHO_MOUSE:
|
|
CPRINTS5("KB eaten by STATE_ECHO_MOUSE: 0x%02x", data);
|
|
output[out_len++] = data;
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_SETREP:
|
|
CPRINTS5("KB eaten by STATE_SETREP: 0x%02x", data);
|
|
set_typematic_delays(data);
|
|
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
case STATE_SEND_TO_MOUSE:
|
|
CPRINTS5("KB eaten by STATE_SEND_TO_MOUSE: 0x%02x",
|
|
data);
|
|
data_port_state = STATE_NORMAL;
|
|
break;
|
|
|
|
default: /* STATE_NORMAL */
|
|
switch (data) {
|
|
case I8042_CMD_GSCANSET: /* also I8042_CMD_SSCANSET */
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_SCANCODE;
|
|
break;
|
|
|
|
case I8042_CMD_SETLEDS:
|
|
/* Chrome OS doesn't have keyboard LEDs, so ignore */
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_SETLEDS;
|
|
break;
|
|
|
|
case I8042_CMD_EX_SETLEDS:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_EX_SETLEDS_1;
|
|
break;
|
|
|
|
case I8042_CMD_DIAG_ECHO:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
output[out_len++] = I8042_CMD_DIAG_ECHO;
|
|
break;
|
|
|
|
case I8042_CMD_GETID: /* fall-thru */
|
|
case I8042_CMD_OK_GETID:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
output[out_len++] = 0xab; /* Regular keyboards */
|
|
output[out_len++] = 0x83;
|
|
break;
|
|
|
|
case I8042_CMD_SETREP:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
data_port_state = STATE_SETREP;
|
|
break;
|
|
|
|
case I8042_CMD_ENABLE:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
keystroke_enable(1);
|
|
keyboard_clear_buffer();
|
|
break;
|
|
|
|
case I8042_CMD_RESET_DIS:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
keystroke_enable(0);
|
|
reset_rate_and_delay();
|
|
keyboard_clear_buffer();
|
|
break;
|
|
|
|
case I8042_CMD_RESET_DEF:
|
|
output[out_len++] = I8042_RET_ACK;
|
|
reset_rate_and_delay();
|
|
keyboard_clear_buffer();
|
|
break;
|
|
|
|
case I8042_CMD_RESET:
|
|
reset_rate_and_delay();
|
|
keyboard_clear_buffer();
|
|
output[out_len++] = I8042_RET_ACK;
|
|
break;
|
|
|
|
case I8042_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 I8042_CMD_SETALL_MB: /* fall-thru */
|
|
case I8042_CMD_SETALL_MBR:
|
|
case I8042_CMD_EX_ENABLE:
|
|
default:
|
|
output[out_len++] = I8042_RET_NAK;
|
|
CPRINTS("KB Unsupported i8042 data 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_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_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_ECHO_MOUSE;
|
|
break;
|
|
|
|
case I8042_SEND_TO_MOUSE:
|
|
data_port_state = STATE_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_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_NORMAL;
|
|
}
|
|
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];
|
|
|
|
while (queue_remove_unit(&from_host, &h)) {
|
|
if (h.type == HOST_COMMAND)
|
|
ret_len = handle_keyboard_command(h.byte, output);
|
|
else
|
|
ret_len = handle_keyboard_data(h.byte, output);
|
|
|
|
i8042_send_to_host(ret_len, output);
|
|
}
|
|
}
|
|
|
|
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();
|
|
uint8_t chr;
|
|
|
|
/* 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);
|
|
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))
|
|
break;
|
|
|
|
/* Handle data waiting for host */
|
|
if (lpc_keyboard_has_char()) {
|
|
/* If interrupts disabled, nothing we can do */
|
|
if (!i8042_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 extra IRQ");
|
|
lpc_keyboard_resume_irq();
|
|
retries = 0;
|
|
break;
|
|
}
|
|
|
|
/* Get a char from buffer. */
|
|
kblog_put('k', to_host.state->head);
|
|
queue_remove_unit(&to_host, &chr);
|
|
kblog_put('K', chr);
|
|
|
|
/* Write to host. */
|
|
lpc_keyboard_put_char(chr, i8042_irq_enabled);
|
|
retries = 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) {
|
|
i8042_send_to_host(len, scan_code);
|
|
task_wake(TASK_ID_KEYPROTO);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
#ifdef CONFIG_CMD_KEYBOARD
|
|
static int command_typematic(int argc, 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: %.6ld\n", get_time().val);
|
|
ccprintf("Deadline: %.6ld\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;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(typematic, command_typematic,
|
|
"[first] [inter]",
|
|
"Get/set typematic delays");
|
|
|
|
|
|
static int command_codeset(int argc, 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;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(codeset, command_codeset,
|
|
"[set]",
|
|
"Get/set keyboard codeset");
|
|
|
|
|
|
static int command_controller_ram(int argc, 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;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(ctrlram, command_controller_ram,
|
|
"index [value]",
|
|
"Get/set keyboard controller RAM");
|
|
|
|
static int command_keyboard_log(int argc, 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;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(kblog, command_keyboard_log,
|
|
"[on | off]",
|
|
"Print or toggle keyboard event log");
|
|
|
|
|
|
static int command_keyboard(int argc, 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;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(kbd, command_keyboard,
|
|
"[on | off]",
|
|
"Print or toggle keyboard info");
|
|
|
|
|
|
static int command_8042_internal(int argc, char **argv)
|
|
{
|
|
int i;
|
|
|
|
ccprintf("data_port_state=%d\n", data_port_state);
|
|
ccprintf("i8042_irq_enabled=%d\n", i8042_irq_enabled);
|
|
ccprintf("keyboard_enabled=%d\n", keyboard_enabled);
|
|
ccprintf("keystroke_enabled=%d\n", keystroke_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) {
|
|
uint8_t entry;
|
|
|
|
queue_peek_units(&to_host, &entry, i, 1);
|
|
|
|
ccprintf("0x%02x, ", entry);
|
|
}
|
|
ccprintf("}\n");
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
static int command_8042(int argc, 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 (!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 {
|
|
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);
|
|
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);
|
|
|
|
/**
|
|
* 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);
|