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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Mathieu Poirier
				Mathieu Poirier