| /* |
| * Copyright (c) 2016 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * Samsung TN debugging code |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/input.h> |
| #include <linux/gpio_keys.h> |
| #include <linux/sec_debug.h> |
| #ifdef CONFIG_SEC_KEY_NOTIFIER |
| #include "../samsung/sec_key_notifier.h" |
| #endif |
| |
| /* Input sequence 9530 */ |
| #define CRASH_COUNT_FIRST 3 |
| #define CRASH_COUNT_SECOND 3 |
| #define CRASH_COUNT_THIRD 3 |
| |
| #define KEY_STATE_DOWN 1 |
| #define KEY_STATE_UP 0 |
| |
| struct tsp_dump_callbacks dump_callbacks; |
| |
| struct crash_key { |
| unsigned int key_code; |
| unsigned int crash_count; |
| }; |
| |
| struct crash_key tsp_dump_key_combination[] = { |
| {KEY_VOLUMEDOWN, CRASH_COUNT_FIRST}, |
| {KEY_POWER, CRASH_COUNT_SECOND}, |
| {KEY_VOLUMEDOWN, CRASH_COUNT_THIRD}, |
| }; |
| |
| struct tsp_dump_key_state { |
| unsigned int key_code; |
| unsigned int state; |
| }; |
| |
| struct tsp_dump_key_state tsp_dump_key_states[] = { |
| {KEY_VOLUMEDOWN, KEY_STATE_UP}, |
| {KEY_VOLUMEUP, KEY_STATE_UP}, |
| {KEY_POWER, KEY_STATE_UP}, |
| {KEY_HOMEPAGE, KEY_STATE_UP}, |
| }; |
| |
| #ifdef CONFIG_SEC_KEY_NOTIFIER |
| static unsigned int __tsp_dump_keys[] = { |
| KEY_POWER, |
| KEY_VOLUMEDOWN, |
| KEY_VOLUMEUP, |
| KEY_HOMEPAGE |
| }; |
| #endif |
| |
| static unsigned int hold_key = KEY_VOLUMEUP; |
| static unsigned int hold_key_hold = KEY_STATE_UP; |
| static size_t check_count; |
| static size_t check_step; |
| |
| static int is_hold_key(unsigned int code) |
| { |
| return (code == hold_key); |
| } |
| |
| static void set_hold_key_hold(int state) |
| { |
| hold_key_hold = state; |
| } |
| |
| static unsigned int is_hold_key_hold(void) |
| { |
| return hold_key_hold; |
| } |
| |
| static unsigned int get_current_step_key_code(void) |
| { |
| return tsp_dump_key_combination[check_step].key_code; |
| } |
| |
| static int is_key_matched_for_current_step(unsigned int code) |
| { |
| return (code == get_current_step_key_code()); |
| } |
| |
| static int is_crash_keys(unsigned int code) |
| { |
| size_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) |
| if (tsp_dump_key_states[i].key_code == code) |
| return 1; |
| return 0; |
| } |
| |
| static size_t get_count_for_next_step(void) |
| { |
| size_t i; |
| size_t count = 0; |
| |
| for (i = 0; i < check_step + 1; i++) |
| count += tsp_dump_key_combination[i].crash_count; |
| return count; |
| } |
| |
| static int is_reaching_count_for_next_step(void) |
| { |
| return (check_count == get_count_for_next_step()); |
| } |
| |
| static size_t get_count_for_panic(void) |
| { |
| size_t i; |
| size_t count = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(tsp_dump_key_combination); i++) |
| count += tsp_dump_key_combination[i].crash_count; |
| return count - 1; |
| } |
| |
| static unsigned int is_key_state_down(unsigned int code) |
| { |
| size_t i; |
| |
| if (is_hold_key(code)) |
| return is_hold_key_hold(); |
| |
| for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) |
| if (tsp_dump_key_states[i].key_code == code) |
| return tsp_dump_key_states[i].state == KEY_STATE_DOWN; |
| return 0; |
| } |
| |
| static void set_key_state_down(unsigned int code) |
| { |
| size_t i; |
| |
| if (is_hold_key(code)) |
| set_hold_key_hold(KEY_STATE_DOWN); |
| |
| for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) |
| if (tsp_dump_key_states[i].key_code == code) |
| tsp_dump_key_states[i].state = KEY_STATE_DOWN; |
| } |
| |
| static void set_key_state_up(unsigned int code) |
| { |
| size_t i; |
| |
| if (is_hold_key(code)) |
| set_hold_key_hold(KEY_STATE_UP); |
| |
| for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) |
| if (tsp_dump_key_states[i].key_code == code) |
| tsp_dump_key_states[i].state = KEY_STATE_UP; |
| } |
| |
| static void increase_step(void) |
| { |
| if (check_step < ARRAY_SIZE(tsp_dump_key_combination)) |
| check_step++; |
| else if (dump_callbacks.inform_dump) |
| dump_callbacks.inform_dump(); |
| } |
| |
| static void reset_step(void) |
| { |
| check_step = 0; |
| } |
| |
| static void increase_count(void) |
| { |
| if (check_count < get_count_for_panic()) |
| check_count++; |
| else if (dump_callbacks.inform_dump) |
| dump_callbacks.inform_dump(); |
| } |
| |
| static void reset_count(void) |
| { |
| check_count = 0; |
| } |
| |
| static int check_tsp_crash_keys(struct notifier_block *nb, |
| unsigned long type, void *data) |
| { |
| #ifndef CONFIG_SEC_KEY_NOTIFIER |
| unsigned int code = (unsigned int)type; |
| int state = *(int *)data; |
| #else |
| struct sec_key_notifier_param *param = data; |
| unsigned int code = param->keycode; |
| int state = param->down; |
| #endif |
| |
| if (!is_crash_keys(code)) |
| return NOTIFY_DONE; |
| |
| if (state == KEY_STATE_DOWN) { |
| /* Duplicated input */ |
| if (is_key_state_down(code)) |
| return NOTIFY_DONE; |
| set_key_state_down(code); |
| |
| if (is_hold_key(code)) { |
| set_hold_key_hold(KEY_STATE_DOWN); |
| return NOTIFY_DONE; |
| } |
| if (is_hold_key_hold()) { |
| if (is_key_matched_for_current_step(code)) { |
| increase_count(); |
| } else { |
| reset_count(); |
| reset_step(); |
| } |
| if (is_reaching_count_for_next_step()) |
| increase_step(); |
| } |
| |
| } else { |
| set_key_state_up(code); |
| if (is_hold_key(code)) { |
| set_hold_key_hold(KEY_STATE_UP); |
| reset_step(); |
| reset_count(); |
| } |
| } |
| return NOTIFY_OK; |
| } |
| |
| #ifndef CONFIG_SEC_KEY_NOTIFIER |
| static struct notifier_block nb_gpio_keys = { |
| .notifier_call = check_tsp_crash_keys |
| }; |
| #else |
| static struct notifier_block seccmn_tsp_crash_key_notifier = { |
| .notifier_call = check_tsp_crash_keys |
| }; |
| #endif |
| |
| static int __init sec_tsp_dumpkey_init(void) |
| { |
| /* only work for debug level is low */ |
| // if (!sec_debug_enter_upload()) |
| #ifndef CONFIG_SEC_KEY_NOTIFIER |
| register_gpio_keys_notifier(&nb_gpio_keys); |
| #else |
| sec_kn_register_notifier(&seccmn_tsp_crash_key_notifier, |
| __tsp_dump_keys, ARRAY_SIZE(__tsp_dump_keys)); |
| #endif |
| return 0; |
| } |
| |
| late_initcall(sec_tsp_dumpkey_init); |