diff options
-rw-r--r-- | common/button.c | 306 | ||||
-rw-r--r-- | include/ec_commands.h | 2 | ||||
-rw-r--r-- | util/ectool.c | 3 |
3 files changed, 310 insertions, 1 deletions
diff --git a/common/button.c b/common/button.c index a536f16a2b..fc1b45167a 100644 --- a/common/button.c +++ b/common/button.c @@ -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 */ diff --git a/include/ec_commands.h b/include/ec_commands.h index a2ee8bd15b..a60ed65729 100644 --- a/include/ec_commands.h +++ b/include/ec_commands.h @@ -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 }; diff --git a/util/ectool.c b/util/ectool.c index 6c562c38fd..ab1da7f394 100644 --- a/util/ectool.c +++ b/util/ectool.c @@ -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 */ |