Commit 154b7a48 authored by Mathieu Poirier's avatar Mathieu Poirier Committed by Dmitry Torokhov

Input: sysrq - allow specifying alternate reset sequence

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: default avatarMathieu Poirier <mathieu.poirier@linaro.org>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent 0799a924
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/input.h> #include <linux/input.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/moduleparam.h>
#include <asm/ptrace.h> #include <asm/ptrace.h>
#include <asm/irq_regs.h> #include <asm/irq_regs.h>
...@@ -576,8 +577,71 @@ struct sysrq_state { ...@@ -576,8 +577,71 @@ struct sysrq_state {
bool active; bool active;
bool need_reinject; bool need_reinject;
bool reinjecting; 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) static void sysrq_reinject_alt_sysrq(struct work_struct *work)
{ {
struct sysrq_state *sysrq = struct sysrq_state *sysrq =
...@@ -604,100 +668,121 @@ static void sysrq_reinject_alt_sysrq(struct work_struct *work) ...@@ -604,100 +668,121 @@ static void sysrq_reinject_alt_sysrq(struct work_struct *work)
} }
} }
static bool sysrq_filter(struct input_handle *handle, static bool sysrq_handle_keypress(struct sysrq_state *sysrq,
unsigned int type, unsigned int code, int value) unsigned int code, int value)
{ {
struct sysrq_state *sysrq = handle->private;
bool was_active = sysrq->active; bool was_active = sysrq->active;
bool suppress; bool suppress;
/* switch (code) {
* Do not filter anything if we are in the process of re-injecting
* Alt+SysRq combination.
*/
if (sysrq->reinjecting)
return false;
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: sysrq->alt = KEY_RESERVED;
suppress = false;
} else if (value != 2) {
sysrq->alt = code;
sysrq->need_reinject = false;
}
break; break;
case EV_KEY: case KEY_SYSRQ:
switch (code) { 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: * Pretend that sysrq was never pressed at all. This
if (!value) { * is needed to properly handle KGDB which will try
/* One of ALTs is being released */ * to release all keys after exiting debugger. If we
if (sysrq->active && code == sysrq->alt_use) * do not clear key bit it KGDB will end up sending
sysrq->active = false; * 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) { default:
sysrq->alt = code; if (sysrq->active && value && value != 2) {
sysrq->need_reinject = false; sysrq->need_reinject = false;
} __handle_sysrq(sysrq_xlate[code], true);
break; }
break;
}
case KEY_SYSRQ: suppress = sysrq->active;
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;
}
/* if (!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);
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) { * If we are not suppressing key presses keep track of
sysrq->need_reinject = false; * keyboard state so we can release keys that have been
__handle_sysrq(sysrq_xlate[code], true); * pressed before entering SysRq mode.
} */
break; 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) { return suppress;
/* }
* 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) static bool sysrq_filter(struct input_handle *handle,
schedule_work(&sysrq->reinject_work); 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)) { * Do not filter anything if we are in the process of re-injecting
/* * Alt+SysRq combination.
* Pass on release events for keys that was pressed before */
* entering SysRq mode. if (sysrq->reinjecting)
*/ return false;
suppress = false;
} switch (type) {
case EV_SYN:
suppress = false;
break;
case EV_KEY:
suppress = sysrq_handle_keypress(sysrq, code, value);
break; break;
default: default:
...@@ -785,7 +870,20 @@ static bool sysrq_handler_registered; ...@@ -785,7 +870,20 @@ static bool sysrq_handler_registered;
static inline void sysrq_register_handler(void) static inline void sysrq_register_handler(void)
{ {
extern unsigned short platform_sysrq_reset_seq[] __weak;
unsigned short key;
int error; 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); error = input_register_handler(&sysrq_handler);
if (error) if (error)
...@@ -802,6 +900,36 @@ static inline void sysrq_unregister_handler(void) ...@@ -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 #else
static inline void sysrq_register_handler(void) static inline void sysrq_register_handler(void)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment