Input: sysrq - allow specifying alternate reset sequence
authorMathieu Poirier <mathieu.poirier@linaro.org>
Mon, 7 Jan 2013 07:23:33 +0000 (23:23 -0800)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Thu, 17 Jan 2013 08:27:52 +0000 (00:27 -0800)
This patch adds keyreset functionality to the sysrq driver. It allows
certain button/key combinations to be used in order to trigger emergency
reboots.

Redefining the '__weak platform_sysrq_reset_seq' variable is required
to trigger the feature.  Alternatively keys can be passed to the driver
via a module parameter.

This functionality comes from the keyreset driver submitted by
Arve Hjønnevåg in the Android kernel.

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

index 16ee6cee07da6b3ba904dea07127f41d98c65133..77fcad4371ce74efa24f6fa72ab6eb39050cc497 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/slab.h>
 #include <linux/input.h>
 #include <linux/uaccess.h>
+#include <linux/moduleparam.h>
 
 #include <asm/ptrace.h>
 #include <asm/irq_regs.h>
@@ -576,8 +577,71 @@ struct sysrq_state {
        bool active;
        bool need_reinject;
        bool reinjecting;
+
+       /* reset sequence handling */
+       bool reset_canceled;
+       unsigned long reset_keybit[BITS_TO_LONGS(KEY_CNT)];
+       int reset_seq_len;
+       int reset_seq_cnt;
+       int reset_seq_version;
 };
 
+#define SYSRQ_KEY_RESET_MAX    20 /* Should be plenty */
+static unsigned short sysrq_reset_seq[SYSRQ_KEY_RESET_MAX];
+static unsigned int sysrq_reset_seq_len;
+static unsigned int sysrq_reset_seq_version = 1;
+
+static void sysrq_parse_reset_sequence(struct sysrq_state *state)
+{
+       int i;
+       unsigned short key;
+
+       state->reset_seq_cnt = 0;
+
+       for (i = 0; i < sysrq_reset_seq_len; i++) {
+               key = sysrq_reset_seq[i];
+
+               if (key == KEY_RESERVED || key > KEY_MAX)
+                       break;
+
+               __set_bit(key, state->reset_keybit);
+               state->reset_seq_len++;
+
+               if (test_bit(key, state->key_down))
+                       state->reset_seq_cnt++;
+       }
+
+       /* Disable reset until old keys are not released */
+       state->reset_canceled = state->reset_seq_cnt != 0;
+
+       state->reset_seq_version = sysrq_reset_seq_version;
+}
+
+static bool 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.
+                */
+               if (value && state->reset_seq_cnt)
+                       state->reset_canceled = true;
+       } else if (value == 0) {
+               /* key release */
+               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;
+               }
+       }
+
+       return false;
+}
+
 static void sysrq_reinject_alt_sysrq(struct work_struct *work)
 {
        struct sysrq_state *sysrq =
@@ -604,100 +668,121 @@ static void sysrq_reinject_alt_sysrq(struct work_struct *work)
        }
 }
 
-static bool sysrq_filter(struct input_handle *handle,
-                        unsigned int type, unsigned int code, int value)
+static bool sysrq_handle_keypress(struct sysrq_state *sysrq,
+                                 unsigned int code, int value)
 {
-       struct sysrq_state *sysrq = handle->private;
        bool was_active = sysrq->active;
        bool suppress;
 
-       /*
-        * Do not filter anything if we are in the process of re-injecting
-        * Alt+SysRq combination.
-        */
-       if (sysrq->reinjecting)
-               return false;
+       switch (code) {
 
-       switch (type) {
+       case KEY_LEFTALT:
+       case KEY_RIGHTALT:
+               if (!value) {
+                       /* One of ALTs is being released */
+                       if (sysrq->active && code == sysrq->alt_use)
+                               sysrq->active = false;
 
-       case EV_SYN:
-               suppress = false;
+                       sysrq->alt = KEY_RESERVED;
+
+               } else if (value != 2) {
+                       sysrq->alt = code;
+                       sysrq->need_reinject = false;
+               }
                break;
 
-       case EV_KEY:
-               switch (code) {
+       case KEY_SYSRQ:
+               if (value == 1 && sysrq->alt != KEY_RESERVED) {
+                       sysrq->active = true;
+                       sysrq->alt_use = sysrq->alt;
+                       /*
+                        * If nothing else will be pressed we'll need
+                        * to re-inject Alt-SysRq keysroke.
+                        */
+                       sysrq->need_reinject = true;
+               }
 
-               case KEY_LEFTALT:
-               case KEY_RIGHTALT:
-                       if (!value) {
-                               /* One of ALTs is being released */
-                               if (sysrq->active && code == sysrq->alt_use)
-                                       sysrq->active = false;
+               /*
+                * Pretend that sysrq was never pressed at all. This
+                * is needed to properly handle KGDB which will try
+                * to release all keys after exiting debugger. If we
+                * do not clear key bit it KGDB will end up sending
+                * release events for Alt and SysRq, potentially
+                * triggering print screen function.
+                */
+               if (sysrq->active)
+                       clear_bit(KEY_SYSRQ, sysrq->handle.dev->key);
 
-                               sysrq->alt = KEY_RESERVED;
+               break;
 
-                       } else if (value != 2) {
-                               sysrq->alt = code;
-                               sysrq->need_reinject = false;
-                       }
-                       break;
+       default:
+               if (sysrq->active && value && value != 2) {
+                       sysrq->need_reinject = false;
+                       __handle_sysrq(sysrq_xlate[code], true);
+               }
+               break;
+       }
 
-               case KEY_SYSRQ:
-                       if (value == 1 && sysrq->alt != KEY_RESERVED) {
-                               sysrq->active = true;
-                               sysrq->alt_use = sysrq->alt;
-                               /*
-                                * If nothing else will be pressed we'll need
-                                * to re-inject Alt-SysRq keysroke.
-                                */
-                               sysrq->need_reinject = true;
-                       }
+       suppress = sysrq->active;
 
-                       /*
-                        * Pretend that sysrq was never pressed at all. This
-                        * is needed to properly handle KGDB which will try
-                        * to release all keys after exiting debugger. If we
-                        * do not clear key bit it KGDB will end up sending
-                        * release events for Alt and SysRq, potentially
-                        * triggering print screen function.
-                        */
-                       if (sysrq->active)
-                               clear_bit(KEY_SYSRQ, handle->dev->key);
+       if (!sysrq->active) {
 
-                       break;
+               /*
+                * See if reset sequence has changed since the last time.
+                */
+               if (sysrq->reset_seq_version != sysrq_reset_seq_version)
+                       sysrq_parse_reset_sequence(sysrq);
 
-               default:
-                       if (sysrq->active && value && value != 2) {
-                               sysrq->need_reinject = false;
-                               __handle_sysrq(sysrq_xlate[code], true);
-                       }
-                       break;
+               /*
+                * If we are not suppressing key presses keep track of
+                * keyboard state so we can release keys that have been
+                * pressed before entering SysRq mode.
+                */
+               if (value)
+                       set_bit(code, sysrq->key_down);
+               else
+                       clear_bit(code, sysrq->key_down);
+
+               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);
                }
 
-               suppress = sysrq->active;
+       } else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) {
+               /*
+                * Pass on release events for keys that was pressed before
+                * entering SysRq mode.
+                */
+               suppress = false;
+       }
 
-               if (!sysrq->active) {
-                       /*
-                        * If we are not suppressing key presses keep track of
-                        * keyboard state so we can release keys that have been
-                        * pressed before entering SysRq mode.
-                        */
-                       if (value)
-                               set_bit(code, sysrq->key_down);
-                       else
-                               clear_bit(code, sysrq->key_down);
+       return suppress;
+}
 
-                       if (was_active)
-                               schedule_work(&sysrq->reinject_work);
+static bool sysrq_filter(struct input_handle *handle,
+                        unsigned int type, unsigned int code, int value)
+{
+       struct sysrq_state *sysrq = handle->private;
+       bool suppress;
 
-               } else if (value == 0 &&
-                          test_and_clear_bit(code, sysrq->key_down)) {
-                       /*
-                        * Pass on release events for keys that was pressed before
-                        * entering SysRq mode.
-                        */
-                       suppress = false;
-               }
+       /*
+        * Do not filter anything if we are in the process of re-injecting
+        * Alt+SysRq combination.
+        */
+       if (sysrq->reinjecting)
+               return false;
+
+       switch (type) {
+
+       case EV_SYN:
+               suppress = false;
+               break;
+
+       case EV_KEY:
+               suppress = sysrq_handle_keypress(sysrq, code, value);
                break;
 
        default:
@@ -785,7 +870,20 @@ static bool sysrq_handler_registered;
 
 static inline void sysrq_register_handler(void)
 {
+       extern unsigned short platform_sysrq_reset_seq[] __weak;
+       unsigned short key;
        int error;
+       int i;
+
+       if (platform_sysrq_reset_seq) {
+               for (i = 0; i < ARRAY_SIZE(sysrq_reset_seq); i++) {
+                       key = platform_sysrq_reset_seq[i];
+                       if (key == KEY_RESERVED || key > KEY_MAX)
+                               break;
+
+                       sysrq_reset_seq[sysrq_reset_seq_len++] = key;
+               }
+       }
 
        error = input_register_handler(&sysrq_handler);
        if (error)
@@ -802,6 +900,36 @@ static inline void sysrq_unregister_handler(void)
        }
 }
 
+static int sysrq_reset_seq_param_set(const char *buffer,
+                                    const struct kernel_param *kp)
+{
+       unsigned long val;
+       int error;
+
+       error = strict_strtoul(buffer, 0, &val);
+       if (error < 0)
+               return error;
+
+       if (val > KEY_MAX)
+               return -EINVAL;
+
+       *((unsigned short *)kp->arg) = val;
+       sysrq_reset_seq_version++;
+
+       return 0;
+}
+
+static struct kernel_param_ops param_ops_sysrq_reset_seq = {
+       .get    = param_get_ushort,
+       .set    = sysrq_reset_seq_param_set,
+};
+
+#define param_check_sysrq_reset_seq(name, p)   \
+       __param_check(name, p, unsigned short)
+
+module_param_array_named(reset_seq, sysrq_reset_seq, sysrq_reset_seq,
+                        &sysrq_reset_seq_len, 0644);
+
 #else
 
 static inline void sysrq_register_handler(void)