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/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…
	
		Reference in a new issue