|
@@ -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)
|