mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02: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/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,11 +668,104 @@ static void sysrq_reinject_alt_sysrq(struct work_struct *work)
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool sysrq_handle_keypress(struct sysrq_state *sysrq,
 | 
			
		||||
				  unsigned int code, int value)
 | 
			
		||||
{
 | 
			
		||||
	bool was_active = sysrq->active;
 | 
			
		||||
	bool suppress;
 | 
			
		||||
 | 
			
		||||
	switch (code) {
 | 
			
		||||
 | 
			
		||||
	case KEY_LEFTALT:
 | 
			
		||||
	case KEY_RIGHTALT:
 | 
			
		||||
		if (!value) {
 | 
			
		||||
			/* One of ALTs is being released */
 | 
			
		||||
			if (sysrq->active && code == sysrq->alt_use)
 | 
			
		||||
				sysrq->active = false;
 | 
			
		||||
 | 
			
		||||
			sysrq->alt = KEY_RESERVED;
 | 
			
		||||
 | 
			
		||||
		} else if (value != 2) {
 | 
			
		||||
			sysrq->alt = code;
 | 
			
		||||
			sysrq->need_reinject = false;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case KEY_SYSRQ:
 | 
			
		||||
		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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*
 | 
			
		||||
		 * 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, sysrq->handle.dev->key);
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		if (sysrq->active && value && value != 2) {
 | 
			
		||||
			sysrq->need_reinject = false;
 | 
			
		||||
			__handle_sysrq(sysrq_xlate[code], true);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
		 * pressed before entering SysRq mode.
 | 
			
		||||
		 */
 | 
			
		||||
		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);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	} 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 was_active = sysrq->active;
 | 
			
		||||
	bool suppress;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
| 
						 | 
				
			
			@ -625,79 +782,7 @@ static bool sysrq_filter(struct input_handle *handle,
 | 
			
		|||
		break;
 | 
			
		||||
 | 
			
		||||
	case EV_KEY:
 | 
			
		||||
		switch (code) {
 | 
			
		||||
 | 
			
		||||
		case KEY_LEFTALT:
 | 
			
		||||
		case KEY_RIGHTALT:
 | 
			
		||||
			if (!value) {
 | 
			
		||||
				/* One of ALTs is being released */
 | 
			
		||||
				if (sysrq->active && code == sysrq->alt_use)
 | 
			
		||||
					sysrq->active = false;
 | 
			
		||||
 | 
			
		||||
				sysrq->alt = KEY_RESERVED;
 | 
			
		||||
 | 
			
		||||
			} else if (value != 2) {
 | 
			
		||||
				sysrq->alt = code;
 | 
			
		||||
				sysrq->need_reinject = false;
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case KEY_SYSRQ:
 | 
			
		||||
			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;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/*
 | 
			
		||||
			 * 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;
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			if (sysrq->active && value && value != 2) {
 | 
			
		||||
				sysrq->need_reinject = false;
 | 
			
		||||
				__handle_sysrq(sysrq_xlate[code], true);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		suppress = sysrq->active;
 | 
			
		||||
 | 
			
		||||
		if (!sysrq->active) {
 | 
			
		||||
			/*
 | 
			
		||||
			 * 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)
 | 
			
		||||
				schedule_work(&sysrq->reinject_work);
 | 
			
		||||
 | 
			
		||||
		} 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;
 | 
			
		||||
		}
 | 
			
		||||
		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…
	
		Reference in a new issue