mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 14:43:16 +00:00
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: Mathieu Poirier <mathieu.poirier@linaro.org> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
parent
0799a924bc
commit
154b7a489a
@ -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,27 +668,12 @@ 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 (type) {
|
||||
|
||||
case EV_SYN:
|
||||
suppress = false;
|
||||
break;
|
||||
|
||||
case EV_KEY:
|
||||
switch (code) {
|
||||
|
||||
case KEY_LEFTALT:
|
||||
@ -662,7 +711,7 @@ static bool sysrq_filter(struct input_handle *handle,
|
||||
* triggering print screen function.
|
||||
*/
|
||||
if (sysrq->active)
|
||||
clear_bit(KEY_SYSRQ, handle->dev->key);
|
||||
clear_bit(KEY_SYSRQ, sysrq->handle.dev->key);
|
||||
|
||||
break;
|
||||
|
||||
@ -677,6 +726,13 @@ static bool sysrq_filter(struct input_handle *handle,
|
||||
suppress = 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
|
||||
* keyboard state so we can release keys that have been
|
||||
@ -690,14 +746,43 @@ static bool sysrq_filter(struct input_handle *handle,
|
||||
if (was_active)
|
||||
schedule_work(&sysrq->reinject_work);
|
||||
|
||||
} else if (value == 0 &&
|
||||
test_and_clear_bit(code, sysrq->key_down)) {
|
||||
if (sysrq_detect_reset_sequence(sysrq, code, value)) {
|
||||
/* 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
|
||||
* entering SysRq mode.
|
||||
*/
|
||||
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;
|
||||
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user