Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+202 −74
Original line number Diff line number Diff line
@@ -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)