button: Implement emulated debug mode using buttons for detachables

BUG=b:36394093
BRANCH=None
TEST=make -j buildall. Verified following actions:
Vup+Vdn (10 seconds) --> Vdn --> Vup : Warm reset AP
Vup+Vdn (10 seconds) --> Vdn -> Power: Exit debug state

Vup+Vdn (10 seconds) --> Vup --> Vdn : Restart chrome
Vup+Vdn (10 seconds) --> Vup --> Power : Exit debug state

Vup+Vdn (10 seconds) --> Vup --> Vup --> Vdn : No action defined
Vup+Vdn (10 seconds) --> Vup --> Vup --> Power: Exit debug state

Vup+Vdn (10 seconds) --> Vup --> Vup --> Vup --> Vdn : Kernel panic
Vup+Vdn (10 seconds) --> Vup --> Vup --> Vup --> Power: Exit debug state

Vup+Vdn (10 seconds) --> Vup --> Vup --> Vup --> Vup: Exit debug state
Vup+Vdn (10 seconds) --> Vdn --> Vdn : Exit debug state

Change-Id: Ic49cc7463f6d8a00f3b4586754feeb3a7d23c371
Signed-off-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/520564
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
This commit is contained in:
Furquan Shaikh
2017-05-27 20:45:16 -07:00
committed by chrome-bot
parent 60ce79badd
commit 5ed0e0f76f
3 changed files with 310 additions and 1 deletions

View File

@@ -6,6 +6,7 @@
/* Button module for Chrome EC */
#include "button.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "gpio.h"
@@ -175,6 +176,12 @@ void button_init(void)
static void button_change_deferred(void);
DECLARE_DEFERRED(button_change_deferred);
#ifdef CONFIG_EMULATED_SYSRQ
static void debug_mode_handle(void);
DECLARE_DEFERRED(debug_mode_handle);
DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, debug_mode_handle, HOOK_PRIO_LAST);
#endif
static void button_change_deferred(void)
{
int i;
@@ -192,6 +199,14 @@ static void button_change_deferred(void)
new_pressed = raw_button_pressed(&buttons[i]);
if (state[i].debounced_pressed != new_pressed) {
state[i].debounced_pressed = new_pressed;
#ifdef CONFIG_EMULATED_SYSRQ
/*
* Calling deferred function for handling debug
* mode so that button change processing is not
* delayed.
*/
hook_call_deferred(&debug_mode_handle_data, 0);
#endif
CPRINTS("Button '%s' was %s",
buttons[i].name, new_pressed ?
"pressed" : "released");
@@ -311,3 +326,294 @@ DECLARE_CONSOLE_COMMAND(button, console_command_button,
"vup|vdown msec",
"Simulate button press");
#endif
#ifdef CONFIG_EMULATED_SYSRQ
enum debug_state {
STATE_DEBUG_NONE,
STATE_DEBUG_CHECK,
STATE_STAGING,
STATE_DEBUG_MODE_ACTIVE,
STATE_SYSRQ_PATH,
STATE_WARM_RESET_PATH,
STATE_SYSRQ_EXEC,
STATE_WARM_RESET_EXEC,
};
#define DEBUG_BTN_POWER (1 << 0)
#define DEBUG_BTN_VOL_UP (1 << 1)
#define DEBUG_BTN_VOL_DN (1 << 2)
#define DEBUG_TIMEOUT (10 * SECOND)
static enum debug_state curr_debug_state = STATE_DEBUG_NONE;
static enum debug_state next_debug_state = STATE_DEBUG_NONE;
static timestamp_t debug_state_deadline;
static int debug_button_hit_count;
static int debug_button_mask(void)
{
int mask = 0;
/* Get power button state */
if (power_button_is_pressed())
mask |= DEBUG_BTN_POWER;
/* Get volume up state */
if (state[BUTTON_VOLUME_UP].debounced_pressed)
mask |= DEBUG_BTN_VOL_UP;
/* Get volume down state */
if (state[BUTTON_VOLUME_DOWN].debounced_pressed)
mask |= DEBUG_BTN_VOL_DN;
return mask;
}
static int debug_button_pressed(int mask)
{
return debug_button_mask() == mask;
}
static int debug_mode_blink_led(void)
{
return ((curr_debug_state != STATE_DEBUG_NONE) &&
(curr_debug_state != STATE_DEBUG_CHECK));
}
static void debug_mode_transition(enum debug_state next_state)
{
timestamp_t now = get_time();
#ifdef CONFIG_LED_COMMON
int curr_blink_state = debug_mode_blink_led();
#endif
/* Cancel any deferred calls. */
hook_call_deferred(&debug_mode_handle_data, -1);
/* Update current debug mode state. */
curr_debug_state = next_state;
/* Set deadline to 10seconds from current time. */
debug_state_deadline.val = now.val + DEBUG_TIMEOUT;
switch (curr_debug_state) {
case STATE_DEBUG_NONE:
/*
* Nothing is done here since some states can transition to
* STATE_DEBUG_NONE in this function. Wait until all other
* states are evaluated to take the action for STATE_NONE.
*/
break;
case STATE_DEBUG_CHECK:
case STATE_STAGING:
/*
* Schedule a deferred call after DEBUG_TIMEOUT to check for
* button state if it does not change during the timeout
* duration.
*/
hook_call_deferred(&debug_mode_handle_data, DEBUG_TIMEOUT);
break;
case STATE_DEBUG_MODE_ACTIVE:
debug_button_hit_count = 0;
break;
case STATE_SYSRQ_PATH:
/*
* Increment debug_button_hit_count and ensure it does not go
* past 3. If it exceeds the limit transition to STATE_NONE.
*/
debug_button_hit_count++;
if (debug_button_hit_count == 4)
curr_debug_state = STATE_DEBUG_NONE;
break;
case STATE_WARM_RESET_PATH:
break;
case STATE_SYSRQ_EXEC:
/*
* Depending upon debug_button_hit_count, send appropriate
* number of sysrq events to host and transition to STATE_NONE.
*/
while (debug_button_hit_count) {
host_send_sysrq('x');
CPRINTS("DEBUG MODE: sysrq-x sent");
debug_button_hit_count--;
}
curr_debug_state = STATE_DEBUG_NONE;
break;
case STATE_WARM_RESET_EXEC:
/* Warm reset the host and transition to STATE_NONE. */
chipset_reset(0);
CPRINTS("DEBUG MODE: Warm reset triggered");
curr_debug_state = STATE_DEBUG_NONE;
break;
default:
curr_debug_state = STATE_DEBUG_NONE;
}
if (curr_debug_state != STATE_DEBUG_NONE)
return;
/* If state machine reached initial state, reset all variables. */
CPRINTS("DEBUG MODE: Exit!");
next_debug_state = STATE_DEBUG_NONE;
debug_state_deadline.val = 0;
debug_button_hit_count = 0;
#ifdef CONFIG_LED_COMMON
if (curr_blink_state)
led_control(EC_LED_ID_SYSRQ_DEBUG_LED, LED_STATE_RESET);
#endif
}
static void debug_mode_handle(void)
{
int mask;
switch (curr_debug_state) {
case STATE_DEBUG_NONE:
/*
* If user pressed Vup+Vdn, check for next 10 seconds to see if
* user keeps holding the keys.
*/
if (debug_button_pressed(DEBUG_BTN_VOL_UP | DEBUG_BTN_VOL_DN))
debug_mode_transition(STATE_DEBUG_CHECK);
break;
case STATE_DEBUG_CHECK:
/*
* If no key is pressed or any key combo other than Vup+Vdn is
* held, then quit debug check mode.
*/
if (!debug_button_pressed(DEBUG_BTN_VOL_UP | DEBUG_BTN_VOL_DN))
debug_mode_transition(STATE_DEBUG_NONE);
else if (timestamp_expired(debug_state_deadline, NULL)) {
/*
* If Vup+Vdn are held down for 10 seconds, then its
* time to enter debug mode.
*/
CPRINTS("DEBUG MODE: Active!");
next_debug_state = STATE_DEBUG_MODE_ACTIVE;
debug_mode_transition(STATE_STAGING);
}
break;
case STATE_STAGING:
mask = debug_button_mask();
/* If no button is pressed, transition to next state. */
if (!mask) {
debug_mode_transition(next_debug_state);
return;
}
/* Exit debug mode if keys are stuck for > 10 seconds. */
if (timestamp_expired(debug_state_deadline, NULL))
debug_mode_transition(STATE_DEBUG_NONE);
else {
timestamp_t now = get_time();
/*
* Schedule a deferred call in case timeout hasn't
* occurred yet.
*/
hook_call_deferred(&debug_mode_handle_data,
(debug_state_deadline.val - now.val));
}
break;
case STATE_DEBUG_MODE_ACTIVE:
mask = debug_button_mask();
/*
* Continue in this state if button is not pressed and timeout
* has not occurred.
*/
if (!mask && !timestamp_expired(debug_state_deadline, NULL))
return;
/* Exit debug mode if valid buttons are not pressed. */
if ((mask != DEBUG_BTN_VOL_UP) && (mask != DEBUG_BTN_VOL_DN)) {
debug_mode_transition(STATE_DEBUG_NONE);
return;
}
/*
* Transition to STAGING state with next state set to:
* 1. SYSRQ_PATH : If Vup was pressed.
* 2. WARM_RESET_PATH: If Vdn was pressed.
*/
if (mask == DEBUG_BTN_VOL_UP)
next_debug_state = STATE_SYSRQ_PATH;
else
next_debug_state = STATE_WARM_RESET_PATH;
debug_mode_transition(STATE_STAGING);
break;
case STATE_SYSRQ_PATH:
mask = debug_button_mask();
/*
* Continue in this state if button is not pressed and timeout
* has not occurred.
*/
if (!mask && !timestamp_expired(debug_state_deadline, NULL))
return;
/* Exit debug mode if valid buttons are not pressed. */
if ((mask != DEBUG_BTN_VOL_UP) && (mask != DEBUG_BTN_VOL_DN)) {
debug_mode_transition(STATE_DEBUG_NONE);
return;
}
if (mask == DEBUG_BTN_VOL_UP) {
/*
* Else transition to STAGING state with next state set
* to SYSRQ_PATH.
*/
next_debug_state = STATE_SYSRQ_PATH;
} else {
/*
* Else if Vdn is pressed, transition to STAGING with
* next state set to SYSRQ_EXEC.
*/
next_debug_state = STATE_SYSRQ_EXEC;
}
debug_mode_transition(STATE_STAGING);
break;
case STATE_WARM_RESET_PATH:
mask = debug_button_mask();
/*
* Continue in this state if button is not pressed and timeout
* has not occurred.
*/
if (!mask && !timestamp_expired(debug_state_deadline, NULL))
return;
/* Exit debug mode if valid buttons are not pressed. */
if (mask != DEBUG_BTN_VOL_UP) {
debug_mode_transition(STATE_DEBUG_NONE);
return;
}
next_debug_state = STATE_WARM_RESET_EXEC;
debug_mode_transition(STATE_STAGING);
break;
case STATE_SYSRQ_EXEC:
case STATE_WARM_RESET_EXEC:
default:
debug_mode_transition(STATE_DEBUG_NONE);
break;
}
}
#ifdef CONFIG_LED_COMMON
static void debug_led_tick(void)
{
static int led_state = LED_STATE_OFF;
if (debug_mode_blink_led()) {
led_state = !led_state;
led_control(EC_LED_ID_SYSRQ_DEBUG_LED, led_state);
}
}
DECLARE_HOOK(HOOK_TICK, debug_led_tick, HOOK_PRIO_DEFAULT);
#endif /* CONFIG_LED_COMMON */
#endif /* CONFIG_EMULATED_SYSRQ */

View File

@@ -1805,6 +1805,8 @@ enum ec_led_id {
EC_LED_ID_RIGHT_LED,
/* LED to indicate recovery mode with HW_REINIT */
EC_LED_ID_RECOVERY_HW_REINIT_LED,
/* LED to indicate sysrq debug mode. */
EC_LED_ID_SYSRQ_DEBUG_LED,
EC_LED_ID_COUNT
};

View File

@@ -266,7 +266,8 @@ BUILD_ASSERT(ARRAY_SIZE(led_color_names) == EC_LED_COLOR_COUNT);
/* Note: depends on enum ec_led_id */
static const char * const led_names[] = {
"battery", "power", "adapter", "left", "right", "recovery_hwreinit"};
"battery", "power", "adapter", "left", "right", "recovery_hwreinit",
"sysrq debug" };
BUILD_ASSERT(ARRAY_SIZE(led_names) == EC_LED_ID_COUNT);
/* Check SBS numerical value range */