blob: 13673b43255923a30383cbc9881cf2b6d5c2f6db [file] [edit]
/* 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.
* Copyright 2013 Google LLC
*
* Tests for keyboard scan deghosting and debouncing.
*/
#include "common.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "keyboard_raw.h"
#include "keyboard_scan.h"
#include "lid_switch.h"
#include "system.h"
#include "task.h"
#include "test_util.h"
#include "timer.h"
#include "util.h"
#define KEYDOWN_DELAY_MS 10
#define KEYDOWN_RETRY 10
#define NO_KEYDOWN_DELAY_MS 100
#define CHECK_KEY_COUNT(old, expected) \
do { \
if (verify_key_presses(old, expected) != EC_SUCCESS) \
return EC_ERROR_UNKNOWN; \
old = fifo_add_count; \
} while (0)
/* Emulated physical key state */
static uint8_t mock_state[KEYBOARD_COLS_MAX];
/* Snapshot of last known key state */
static uint8_t key_state[KEYBOARD_COLS_MAX];
/* Counters for key state changes (UP/DOWN) */
static int key_state_change[KEYBOARD_COLS_MAX][KEYBOARD_ROWS];
static int total_key_state_change;
static int column_driven;
static int fifo_add_count;
static int lid_open;
#ifdef EMU_BUILD
static int hibernated;
static int reset_called;
#endif
/*
* Helper method to wake a given task, and provide immediate opportunity to run.
*/
static void task_wake_then_sleep_1ms(int task_id)
{
task_wake(task_id);
crec_msleep(1);
}
#ifdef CONFIG_LID_SWITCH
int lid_is_open(void)
{
return lid_open;
}
#endif
void keyboard_raw_drive_column(int out)
{
column_driven = out;
}
int keyboard_raw_read_rows(void)
{
int i;
int r = 0;
if (column_driven == KEYBOARD_COLUMN_NONE) {
return 0;
} else if (column_driven == KEYBOARD_COLUMN_ALL) {
for (i = 0; i < KEYBOARD_COLS_MAX; ++i)
r |= mock_state[i];
return r;
} else {
return mock_state[column_driven];
}
}
int mkbp_keyboard_add(const uint8_t *buffp)
{
int c, r;
fifo_add_count++;
for (c = 0; c < KEYBOARD_COLS_MAX; c++) {
uint8_t diff = key_state[c] ^ buffp[c];
for (r = 0; r < KEYBOARD_ROWS; r++) {
if (diff & BIT(r)) {
key_state_change[c][r]++;
total_key_state_change++;
}
}
}
/* Save a snapshot. */
memcpy(key_state, buffp, sizeof(key_state));
return EC_SUCCESS;
}
#ifdef EMU_BUILD
void system_hibernate(uint32_t s, uint32_t us)
{
hibernated = 1;
}
void chipset_reset(void)
{
reset_called = 1;
}
#endif
#define mock_defined_key(k, p) mock_key(KEYBOARD_ROW_##k, KEYBOARD_COL_##k, p)
#define mock_default_key(k, p) \
mock_key(KEYBOARD_DEFAULT_ROW_##k, KEYBOARD_DEFAULT_COL_##k, p)
static void mock_key(int r, int c, int keydown)
{
ccprintf(" %s (%d, %d)\n", keydown ? "Pressing" : "Releasing", r, c);
if (keydown)
mock_state[c] |= (1 << r);
else
mock_state[c] &= ~(1 << r);
}
static void reset_key_state(void)
{
memset(mock_state, 0, sizeof(mock_state));
memset(key_state, 0, sizeof(key_state));
memset(key_state_change, 0, sizeof(key_state_change));
task_wake(TASK_ID_KEYSCAN);
crec_msleep(NO_KEYDOWN_DELAY_MS);
total_key_state_change = 0;
}
static int expect_keychange(void)
{
int old_count = fifo_add_count;
int retry = KEYDOWN_RETRY;
task_wake(TASK_ID_KEYSCAN);
while (retry--) {
crec_msleep(KEYDOWN_DELAY_MS);
if (fifo_add_count > old_count)
return EC_SUCCESS;
}
return EC_ERROR_UNKNOWN;
}
static int expect_no_keychange(void)
{
int old_count = fifo_add_count;
task_wake(TASK_ID_KEYSCAN);
crec_msleep(NO_KEYDOWN_DELAY_MS);
return (fifo_add_count == old_count) ? EC_SUCCESS : EC_ERROR_UNKNOWN;
}
static int host_command_simulate(int r, int c, int keydown)
{
struct ec_params_mkbp_simulate_key params;
params.col = c;
params.row = r;
params.pressed = keydown;
return test_send_host_command(EC_CMD_MKBP_SIMULATE_KEY, 0, &params,
sizeof(params), NULL, 0);
}
static int verify_key_presses(int old, int expected)
{
int retry = KEYDOWN_RETRY;
if (expected == 0) {
crec_msleep(NO_KEYDOWN_DELAY_MS);
return (fifo_add_count == old) ? EC_SUCCESS : EC_ERROR_UNKNOWN;
} else {
while (retry--) {
crec_msleep(KEYDOWN_DELAY_MS);
if (fifo_add_count == old + expected)
return EC_SUCCESS;
}
return EC_ERROR_UNKNOWN;
}
}
static int set_cols_test(void)
{
const uint8_t cols = keyboard_get_cols();
keyboard_set_cols(cols + 1);
TEST_ASSERT(keyboard_get_cols() == cols + 1);
keyboard_set_cols(cols);
return EC_SUCCESS;
}
static int deghost_test(void)
{
reset_key_state();
/* Test we can detect a keypress */
mock_key(1, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 1, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
/* (1, 1) (1, 2) (2, 1) (2, 2) form ghosting keys */
mock_key(1, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(2, 2, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 2, 1);
mock_key(2, 1, 1);
TEST_ASSERT(expect_no_keychange() == EC_SUCCESS);
mock_key(2, 1, 0);
mock_key(1, 2, 0);
TEST_ASSERT(expect_no_keychange() == EC_SUCCESS);
mock_key(2, 2, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 1, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
/* (1, 1) (2, 0) (2, 1) don't form ghosting keys */
mock_key(1, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(2, 0, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 0, 1);
mock_key(2, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 0, 0);
mock_key(2, 1, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(2, 0, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 1, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
return EC_SUCCESS;
}
static int strict_debounce_test(void)
{
reset_key_state();
ccprintf("Test key press & hold.\n");
mock_key(1, 1, 1);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 1, "%d");
TEST_EQ(total_key_state_change, 1, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test a short stroke.\n");
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_no_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 0, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test ripples being suppressed.\n");
/* DOWN */
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 1, "%d");
TEST_EQ(total_key_state_change, 1, "%d");
/* UP */
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 2, "%d");
TEST_EQ(total_key_state_change, 2, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test simultaneous strokes.\n");
mock_key(1, 1, 1);
mock_key(2, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 1, "%d");
TEST_EQ(key_state_change[1][2], 1, "%d");
TEST_EQ(total_key_state_change, 2, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test simultaneous strokes in two columns.\n");
mock_key(1, 1, 1);
mock_key(1, 2, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 1, "%d");
TEST_EQ(key_state_change[2][1], 1, "%d");
TEST_EQ(total_key_state_change, 2, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test normal & short simultaneous strokes.\n");
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(2, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 0, "%d");
TEST_EQ(key_state_change[1][2], 1, "%d");
TEST_EQ(total_key_state_change, 1, "%d");
ccprintf("Pass.\n");
reset_key_state();
ccprintf("Test normal & short simultaneous strokes in two columns.\n");
reset_key_state();
mock_key(1, 1, 1);
task_wake(TASK_ID_KEYSCAN);
mock_key(1, 2, 1);
task_wake(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake(TASK_ID_KEYSCAN);
TEST_EQ(expect_keychange(), EC_SUCCESS, "%d");
TEST_EQ(key_state_change[1][1], 0, "%d");
TEST_EQ(key_state_change[2][1], 1, "%d");
TEST_EQ(total_key_state_change, 1, "%d");
ccprintf("Pass.\n");
return EC_SUCCESS;
}
static int debounce_test(void)
{
int old_count = fifo_add_count;
int i;
reset_key_state();
/* One brief keypress is detected. */
crec_msleep(40);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 2);
/* Brief bounce, followed by continuous press is detected as one. */
crec_msleep(40);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 1);
/* Brief lifting, then re-presseing is detected as new keypress. */
crec_msleep(40);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 2);
/* One bouncy re-contact while lifting is ignored. */
crec_msleep(40);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
mock_key(1, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 1);
/*
* Debounce interval of first key is not affected by continued
* activity of other keys.
*/
crec_msleep(40);
/* Push the first key */
mock_key(0, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
/*
* Push down each subsequent key, until all 8 are pressed, each
* time bouncing the former one once.
*/
for (i = 1; i < 8; i++) {
mock_key(i, 1, 1);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(3);
mock_key(i - 1, 1, 0);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(1);
mock_key(i - 1, 1, 1);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(1);
}
/* Verify that the bounces were. ignored */
CHECK_KEY_COUNT(old_count, 8);
/*
* Now briefly lift and re-press the first one, which should now be past
* its debounce interval
*/
mock_key(0, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 1);
mock_key(0, 1, 1);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
CHECK_KEY_COUNT(old_count, 1);
/* For good measure, release all keys before proceeding. */
for (i = 0; i < 8; i++)
mock_key(i, 1, 0);
task_wake_then_sleep_1ms(TASK_ID_KEYSCAN);
return EC_SUCCESS;
}
static int simulate_key_test(void)
{
int old_count;
reset_key_state();
task_wake(TASK_ID_KEYSCAN);
crec_msleep(40); /* Wait for debouncing to settle */
old_count = fifo_add_count;
host_command_simulate(1, 1, 1);
TEST_ASSERT(fifo_add_count > old_count);
crec_msleep(40);
old_count = fifo_add_count;
host_command_simulate(1, 1, 0);
TEST_ASSERT(fifo_add_count > old_count);
crec_msleep(40);
return EC_SUCCESS;
}
#ifdef EMU_BUILD
static int wait_variable_set(int *var)
{
int retry = KEYDOWN_RETRY;
*var = 0;
task_wake(TASK_ID_KEYSCAN);
while (retry--) {
crec_msleep(KEYDOWN_DELAY_MS);
if (*var == 1)
return EC_SUCCESS;
}
return EC_ERROR_UNKNOWN;
}
static int verify_variable_not_set(int *var)
{
*var = 0;
task_wake(TASK_ID_KEYSCAN);
crec_msleep(NO_KEYDOWN_DELAY_MS);
return *var ? EC_ERROR_UNKNOWN : EC_SUCCESS;
}
static int runtime_key_test(void)
{
reset_key_state();
/* Alt-VolUp-H triggers system hibernation */
mock_defined_key(LEFT_ALT, 1);
mock_default_key(VOL_UP, 1);
mock_defined_key(KEY_H, 1);
TEST_ASSERT(wait_variable_set(&hibernated) == EC_SUCCESS);
mock_defined_key(LEFT_ALT, 0);
mock_default_key(VOL_UP, 0);
mock_defined_key(KEY_H, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
/* Alt-VolUp-R triggers chipset reset */
mock_defined_key(RIGHT_ALT, 1);
mock_default_key(VOL_UP, 1);
mock_defined_key(KEY_R, 1);
TEST_ASSERT(wait_variable_set(&reset_called) == EC_SUCCESS);
mock_defined_key(RIGHT_ALT, 0);
mock_default_key(VOL_UP, 0);
mock_defined_key(KEY_R, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
/* Must press exactly 3 keys to trigger runtime keys */
mock_defined_key(LEFT_ALT, 1);
mock_defined_key(KEY_H, 1);
mock_defined_key(KEY_R, 1);
mock_default_key(VOL_UP, 1);
TEST_ASSERT(verify_variable_not_set(&hibernated) == EC_SUCCESS);
TEST_ASSERT(verify_variable_not_set(&reset_called) == EC_SUCCESS);
mock_default_key(VOL_UP, 0);
mock_defined_key(KEY_R, 0);
mock_defined_key(KEY_H, 0);
mock_defined_key(LEFT_ALT, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
return EC_SUCCESS;
}
#endif
#ifdef CONFIG_LID_SWITCH
static int lid_test(void)
{
reset_key_state();
crec_msleep(40); /* Allow debounce to settle */
lid_open = 0;
hook_notify(HOOK_LID_CHANGE);
crec_msleep(1); /* Allow hooks to run */
mock_key(1, 1, 1);
TEST_ASSERT(expect_no_keychange() == EC_SUCCESS);
mock_key(1, 1, 0);
TEST_ASSERT(expect_no_keychange() == EC_SUCCESS);
lid_open = 1;
hook_notify(HOOK_LID_CHANGE);
crec_msleep(1); /* Allow hooks to run */
mock_key(1, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
mock_key(1, 1, 0);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
return EC_SUCCESS;
}
#endif
static int power_button_counter_divider;
static int power_button_counter;
int power_button_signal_asserted(void)
{
if (!power_button_counter_divider)
return 0;
return !(power_button_counter++ % power_button_counter_divider);
}
static int power_button_mask_test(void)
{
/*
* Make power_button_raw_pressed return 1 every 28 calls: 1, 0, 0, ....
* The first two calls are for column 0. The next two are also for
* column 0 but for debounce-rescan. Since there are 13 columns, there
* will be 13x2 + 2 = 28 calls for scanning a whole matrix.
*/
ccprintf("\nTest power button change during a single column scan.");
power_button_counter_divider = 28;
power_button_counter = 0;
reset_key_state();
crec_msleep(40);
mock_key(1, 1, 1);
TEST_ASSERT(expect_keychange() == EC_SUCCESS);
/*
* Make power_button_raw_pressed return 1 continuously. Refresh key
* should get it back because we know all columns driven by the GSC
* if the power button and refresh key are pressed at boot.
*/
ccprintf("\nTest continuous power button press.\n");
power_button_counter_divider = 1;
power_button_counter = 0;
reset_key_state();
crec_msleep(40);
mock_key(KEYBOARD_ROW_REFRESH, KEYBOARD_COL_REFRESH, 1);
mock_key(1, 1, 1);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(40);
TEST_EQ(key_state_change[KEYBOARD_ROW_REFRESH][KEYBOARD_COL_REFRESH], 1,
"%d");
TEST_EQ(key_state_change[1][1], 1, "%d");
power_button_counter_divider = 0;
return EC_SUCCESS;
}
static int test_check_boot_esc(void)
{
TEST_ASSERT(keyboard_scan_get_boot_keys() == BIT(BOOT_KEY_ESC));
mock_key(KEYBOARD_ROW_ESC, KEYBOARD_COL_ESC, 0);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(40);
TEST_ASSERT(keyboard_scan_get_boot_keys() == 0);
return EC_SUCCESS;
}
static int test_check_boot_down(void)
{
TEST_ASSERT(keyboard_scan_get_boot_keys() ==
(BIT(BOOT_KEY_DOWN_ARROW) | BIT(BOOT_KEY_REFRESH)));
mock_key(6, 11, 0);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(40);
TEST_ASSERT(keyboard_scan_get_boot_keys() == BIT(BOOT_KEY_REFRESH));
mock_key(KEYBOARD_ROW_REFRESH, KEYBOARD_COL_REFRESH, 0);
task_wake(TASK_ID_KEYSCAN);
crec_msleep(40);
TEST_ASSERT(keyboard_scan_get_boot_keys() == 0);
return EC_SUCCESS;
}
void test_init(void)
{
uint32_t state;
system_get_scratchpad(&state);
gpio_set_level(GPIO_POWER_BUTTON_L, 1);
if (state & TEST_STATE_MASK(TEST_STATE_STEP_2)) {
/* Power-F3-ESC */
system_set_reset_flags(system_get_reset_flags() |
EC_RESET_FLAG_RESET_PIN);
mock_key(KEYBOARD_ROW_ESC, KEYBOARD_COL_ESC, 1);
} else if (state & TEST_STATE_MASK(TEST_STATE_STEP_3)) {
/* Power-F3-Down */
system_set_reset_flags(system_get_reset_flags() |
EC_RESET_FLAG_RESET_PIN);
mock_key(KEYBOARD_ROW_REFRESH, KEYBOARD_COL_REFRESH, 1);
mock_key(6, 11, 1);
}
}
static void run_test_step1(void)
{
lid_open = 1;
hook_notify(HOOK_LID_CHANGE);
test_reset();
crec_msleep(1);
RUN_TEST(set_cols_test);
RUN_TEST(deghost_test);
if (IS_ENABLED(CONFIG_KEYBOARD_STRICT_DEBOUNCE))
RUN_TEST(strict_debounce_test);
else
RUN_TEST(debounce_test);
if (0) /* crbug.com/976974 */
RUN_TEST(simulate_key_test);
#ifdef EMU_BUILD
RUN_TEST(runtime_key_test);
#endif
#ifdef CONFIG_LID_SWITCH
RUN_TEST(lid_test);
#endif
RUN_TEST(power_button_mask_test);
if (test_get_error_count())
test_reboot_to_next_step(TEST_STATE_FAILED);
else
test_reboot_to_next_step(TEST_STATE_STEP_2);
}
static void run_test_step2(void)
{
lid_open = 1;
hook_notify(HOOK_LID_CHANGE);
test_reset();
crec_msleep(1);
RUN_TEST(test_check_boot_esc);
if (test_get_error_count())
test_reboot_to_next_step(TEST_STATE_FAILED);
else
test_reboot_to_next_step(TEST_STATE_STEP_3);
}
static void run_test_step3(void)
{
lid_open = 1;
hook_notify(HOOK_LID_CHANGE);
test_reset();
crec_msleep(1);
RUN_TEST(test_check_boot_down);
if (test_get_error_count())
test_reboot_to_next_step(TEST_STATE_FAILED);
else
test_reboot_to_next_step(TEST_STATE_PASSED);
}
void test_run_step(uint32_t state)
{
if (state & TEST_STATE_MASK(TEST_STATE_STEP_1))
run_test_step1();
else if (state & TEST_STATE_MASK(TEST_STATE_STEP_2))
run_test_step2();
else if (state & TEST_STATE_MASK(TEST_STATE_STEP_3))
run_test_step3();
}
int test_task(void *data)
{
test_run_multistep();
return EC_SUCCESS;
}
void run_test(int argc, const char **argv)
{
crec_msleep(30); /* Wait for TASK_ID_TEST to initialize */
task_wake(TASK_ID_TEST);
}