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,27 +668,12 @@ static void sysrq_reinject_alt_sysrq(struct work_struct *work) ...@@ -604,27 +668,12 @@ 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;
/*
* 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:
switch (code) { switch (code) {
case KEY_LEFTALT: case KEY_LEFTALT:
...@@ -662,7 +711,7 @@ static bool sysrq_filter(struct input_handle *handle, ...@@ -662,7 +711,7 @@ static bool sysrq_filter(struct input_handle *handle,
* triggering print screen function. * triggering print screen function.
*/ */
if (sysrq->active) if (sysrq->active)
clear_bit(KEY_SYSRQ, handle->dev->key); clear_bit(KEY_SYSRQ, sysrq->handle.dev->key);
break; break;
...@@ -677,6 +726,13 @@ static bool sysrq_filter(struct input_handle *handle, ...@@ -677,6 +726,13 @@ static bool sysrq_filter(struct input_handle *handle,
suppress = sysrq->active; suppress = sysrq->active;
if (!sysrq->active) { if (!sysrq->active) {
/*
* See if reset sequence has changed since the last time.
*/
if (sysrq->reset_seq_version != sysrq_reset_seq_version)
sysrq_parse_reset_sequence(sysrq);
/* /*
* If we are not suppressing key presses keep track of * If we are not suppressing key presses keep track of
* keyboard state so we can release keys that have been * keyboard state so we can release keys that have been
...@@ -690,14 +746,43 @@ static bool sysrq_filter(struct input_handle *handle, ...@@ -690,14 +746,43 @@ static bool sysrq_filter(struct input_handle *handle,
if (was_active) if (was_active)
schedule_work(&sysrq->reinject_work); schedule_work(&sysrq->reinject_work);
} else if (value == 0 && if (sysrq_detect_reset_sequence(sysrq, code, value)) {
test_and_clear_bit(code, sysrq->key_down)) { /* Force emergency reboot */
__handle_sysrq(sysrq_xlate[KEY_B], false);
}
} else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) {
/* /*
* Pass on release events for keys that was pressed before * Pass on release events for keys that was pressed before
* entering SysRq mode. * entering SysRq mode.
*/ */
suppress = false; suppress = false;
} }
return suppress;
}
static bool sysrq_filter(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct sysrq_state *sysrq = handle->private;
bool suppress;
/*
* 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; 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