Input: sysrq - supplement reset sequence with timeout functionality
authorMathieu J. Poirier <mathieu.poirier@linaro.org>
Tue, 2 Apr 2013 05:14:19 +0000 (22:14 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Tue, 2 Apr 2013 05:22:19 +0000 (22:22 -0700)
Some devices have too few buttons, which it makes it hard to have
a reset combo that won't trigger automatically.  As such a
timeout functionality that requires the combination to be held for
a given amount of time before triggering is introduced.

If a key combo is recognized and held for a 'timeout' amount of time,
the system triggers a reset.  If the timeout value is omitted the
driver simply ignores the functionality.

Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/tty/sysrq.c

index 3687f0cad6421c01c4e0e62d2345165c80765dc9..b6b267d939a07bdba1c776bb7f5984fc76f77ed0 100644 (file)
@@ -43,6 +43,7 @@
 #include <linux/input.h>
 #include <linux/uaccess.h>
 #include <linux/moduleparam.h>
+#include <linux/jiffies.h>
 
 #include <asm/ptrace.h>
 #include <asm/irq_regs.h>
@@ -51,6 +52,9 @@
 static int __read_mostly sysrq_enabled = SYSRQ_DEFAULT_ENABLE;
 static bool __read_mostly sysrq_always_enabled;
 
+unsigned short platform_sysrq_reset_seq[] __weak = { KEY_RESERVED };
+int sysrq_reset_downtime_ms __weak;
+
 static bool sysrq_on(void)
 {
        return sysrq_enabled || sysrq_always_enabled;
@@ -586,6 +590,7 @@ struct sysrq_state {
        int reset_seq_len;
        int reset_seq_cnt;
        int reset_seq_version;
+       struct timer_list keyreset_timer;
 };
 
 #define SYSRQ_KEY_RESET_MAX    20 /* Should be plenty */
@@ -619,29 +624,51 @@ static void sysrq_parse_reset_sequence(struct sysrq_state *state)
        state->reset_seq_version = sysrq_reset_seq_version;
 }
 
-static bool sysrq_detect_reset_sequence(struct sysrq_state *state,
+static void sysrq_do_reset(unsigned long dummy)
+{
+       __handle_sysrq(sysrq_xlate[KEY_B], false);
+}
+
+static void sysrq_handle_reset_request(struct sysrq_state *state)
+{
+       if (sysrq_reset_downtime_ms)
+               mod_timer(&state->keyreset_timer,
+                       jiffies + msecs_to_jiffies(sysrq_reset_downtime_ms));
+       else
+               sysrq_do_reset(0);
+}
+
+static void sysrq_detect_reset_sequence(struct sysrq_state *state,
                                        unsigned int code, int value)
 {
        if (!test_bit(code, state->reset_keybit)) {
                /*
                 * Pressing any key _not_ in reset sequence cancels
-                * the reset sequence.
+                * the reset sequence.  Also cancelling the timer in
+                * case additional keys were pressed after a reset
+                * has been requested.
                 */
-               if (value && state->reset_seq_cnt)
+               if (value && state->reset_seq_cnt) {
                        state->reset_canceled = true;
+                       del_timer(&state->keyreset_timer);
+               }
        } else if (value == 0) {
-               /* key release */
+               /*
+                * Key release - all keys in the reset sequence need
+                * to be pressed and held for the reset timeout
+                * to hold.
+                */
+               del_timer(&state->keyreset_timer);
+
                if (--state->reset_seq_cnt == 0)
                        state->reset_canceled = false;
        } else if (value == 1) {
                /* key press, not autorepeat */
                if (++state->reset_seq_cnt == state->reset_seq_len &&
                    !state->reset_canceled) {
-                       return true;
+                       sysrq_handle_reset_request(state);
                }
        }
-
-       return false;
 }
 
 static void sysrq_reinject_alt_sysrq(struct work_struct *work)
@@ -748,10 +775,8 @@ static bool sysrq_handle_keypress(struct sysrq_state *sysrq,
                if (was_active)
                        schedule_work(&sysrq->reinject_work);
 
-               if (sysrq_detect_reset_sequence(sysrq, code, value)) {
-                       /* Force emergency reboot */
-                       __handle_sysrq(sysrq_xlate[KEY_B], false);
-               }
+               /* Check for reset sequence */
+               sysrq_detect_reset_sequence(sysrq, code, value);
 
        } else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) {
                /*
@@ -812,6 +837,7 @@ static int sysrq_connect(struct input_handler *handler,
        sysrq->handle.handler = handler;
        sysrq->handle.name = "sysrq";
        sysrq->handle.private = sysrq;
+       setup_timer(&sysrq->keyreset_timer, sysrq_do_reset, 0);
 
        error = input_register_handle(&sysrq->handle);
        if (error) {
@@ -841,6 +867,7 @@ static void sysrq_disconnect(struct input_handle *handle)
 
        input_close_device(handle);
        cancel_work_sync(&sysrq->reinject_work);
+       del_timer_sync(&sysrq->keyreset_timer);
        input_unregister_handle(handle);
        kfree(sysrq);
 }
@@ -870,8 +897,6 @@ static struct input_handler sysrq_handler = {
 
 static bool sysrq_handler_registered;
 
-unsigned short platform_sysrq_reset_seq[] __weak = { KEY_RESERVED };
-
 static inline void sysrq_register_handler(void)
 {
        unsigned short key;
@@ -931,6 +956,8 @@ static struct kernel_param_ops param_ops_sysrq_reset_seq = {
 module_param_array_named(reset_seq, sysrq_reset_seq, sysrq_reset_seq,
                         &sysrq_reset_seq_len, 0644);
 
+module_param_named(sysrq_downtime_ms, sysrq_reset_downtime_ms, int, 0644);
+
 #else
 
 static inline void sysrq_register_handler(void)