mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-04-21 05:34:00 +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
1 changed files with 202 additions and 74 deletions
|
@ -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 {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
* 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,
|
||||||
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,
|
||||||
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;
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue