Commit 154b7a489a5b1d808323b933b04864958c2f1056
Committed by
Dmitry Torokhov
1 parent
0799a924bc
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
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>
Showing 1 changed file with 202 additions and 74 deletions Side-by-side Diff
drivers/tty/sysrq.c
... | ... | @@ -41,6 +41,7 @@ |
41 | 41 | #include <linux/slab.h> |
42 | 42 | #include <linux/input.h> |
43 | 43 | #include <linux/uaccess.h> |
44 | +#include <linux/moduleparam.h> | |
44 | 45 | |
45 | 46 | #include <asm/ptrace.h> |
46 | 47 | #include <asm/irq_regs.h> |
47 | 48 | |
... | ... | @@ -576,8 +577,71 @@ |
576 | 577 | bool active; |
577 | 578 | bool need_reinject; |
578 | 579 | bool reinjecting; |
580 | + | |
581 | + /* reset sequence handling */ | |
582 | + bool reset_canceled; | |
583 | + unsigned long reset_keybit[BITS_TO_LONGS(KEY_CNT)]; | |
584 | + int reset_seq_len; | |
585 | + int reset_seq_cnt; | |
586 | + int reset_seq_version; | |
579 | 587 | }; |
580 | 588 | |
589 | +#define SYSRQ_KEY_RESET_MAX 20 /* Should be plenty */ | |
590 | +static unsigned short sysrq_reset_seq[SYSRQ_KEY_RESET_MAX]; | |
591 | +static unsigned int sysrq_reset_seq_len; | |
592 | +static unsigned int sysrq_reset_seq_version = 1; | |
593 | + | |
594 | +static void sysrq_parse_reset_sequence(struct sysrq_state *state) | |
595 | +{ | |
596 | + int i; | |
597 | + unsigned short key; | |
598 | + | |
599 | + state->reset_seq_cnt = 0; | |
600 | + | |
601 | + for (i = 0; i < sysrq_reset_seq_len; i++) { | |
602 | + key = sysrq_reset_seq[i]; | |
603 | + | |
604 | + if (key == KEY_RESERVED || key > KEY_MAX) | |
605 | + break; | |
606 | + | |
607 | + __set_bit(key, state->reset_keybit); | |
608 | + state->reset_seq_len++; | |
609 | + | |
610 | + if (test_bit(key, state->key_down)) | |
611 | + state->reset_seq_cnt++; | |
612 | + } | |
613 | + | |
614 | + /* Disable reset until old keys are not released */ | |
615 | + state->reset_canceled = state->reset_seq_cnt != 0; | |
616 | + | |
617 | + state->reset_seq_version = sysrq_reset_seq_version; | |
618 | +} | |
619 | + | |
620 | +static bool sysrq_detect_reset_sequence(struct sysrq_state *state, | |
621 | + unsigned int code, int value) | |
622 | +{ | |
623 | + if (!test_bit(code, state->reset_keybit)) { | |
624 | + /* | |
625 | + * Pressing any key _not_ in reset sequence cancels | |
626 | + * the reset sequence. | |
627 | + */ | |
628 | + if (value && state->reset_seq_cnt) | |
629 | + state->reset_canceled = true; | |
630 | + } else if (value == 0) { | |
631 | + /* key release */ | |
632 | + if (--state->reset_seq_cnt == 0) | |
633 | + state->reset_canceled = false; | |
634 | + } else if (value == 1) { | |
635 | + /* key press, not autorepeat */ | |
636 | + if (++state->reset_seq_cnt == state->reset_seq_len && | |
637 | + !state->reset_canceled) { | |
638 | + return true; | |
639 | + } | |
640 | + } | |
641 | + | |
642 | + return false; | |
643 | +} | |
644 | + | |
581 | 645 | static void sysrq_reinject_alt_sysrq(struct work_struct *work) |
582 | 646 | { |
583 | 647 | struct sysrq_state *sysrq = |
584 | 648 | |
585 | 649 | |
586 | 650 | |
587 | 651 | |
588 | 652 | |
589 | 653 | |
590 | 654 | |
591 | 655 | |
592 | 656 | |
593 | 657 | |
594 | 658 | |
595 | 659 | |
596 | 660 | |
597 | 661 | |
598 | 662 | |
599 | 663 | |
600 | 664 | |
... | ... | @@ -604,102 +668,123 @@ |
604 | 668 | } |
605 | 669 | } |
606 | 670 | |
607 | -static bool sysrq_filter(struct input_handle *handle, | |
608 | - unsigned int type, unsigned int code, int value) | |
671 | +static bool sysrq_handle_keypress(struct sysrq_state *sysrq, | |
672 | + unsigned int code, int value) | |
609 | 673 | { |
610 | - struct sysrq_state *sysrq = handle->private; | |
611 | 674 | bool was_active = sysrq->active; |
612 | 675 | bool suppress; |
613 | 676 | |
614 | - /* | |
615 | - * Do not filter anything if we are in the process of re-injecting | |
616 | - * Alt+SysRq combination. | |
617 | - */ | |
618 | - if (sysrq->reinjecting) | |
619 | - return false; | |
677 | + switch (code) { | |
620 | 678 | |
621 | - switch (type) { | |
679 | + case KEY_LEFTALT: | |
680 | + case KEY_RIGHTALT: | |
681 | + if (!value) { | |
682 | + /* One of ALTs is being released */ | |
683 | + if (sysrq->active && code == sysrq->alt_use) | |
684 | + sysrq->active = false; | |
622 | 685 | |
623 | - case EV_SYN: | |
624 | - suppress = false; | |
686 | + sysrq->alt = KEY_RESERVED; | |
687 | + | |
688 | + } else if (value != 2) { | |
689 | + sysrq->alt = code; | |
690 | + sysrq->need_reinject = false; | |
691 | + } | |
625 | 692 | break; |
626 | 693 | |
627 | - case EV_KEY: | |
628 | - switch (code) { | |
694 | + case KEY_SYSRQ: | |
695 | + if (value == 1 && sysrq->alt != KEY_RESERVED) { | |
696 | + sysrq->active = true; | |
697 | + sysrq->alt_use = sysrq->alt; | |
698 | + /* | |
699 | + * If nothing else will be pressed we'll need | |
700 | + * to re-inject Alt-SysRq keysroke. | |
701 | + */ | |
702 | + sysrq->need_reinject = true; | |
703 | + } | |
629 | 704 | |
630 | - case KEY_LEFTALT: | |
631 | - case KEY_RIGHTALT: | |
632 | - if (!value) { | |
633 | - /* One of ALTs is being released */ | |
634 | - if (sysrq->active && code == sysrq->alt_use) | |
635 | - sysrq->active = false; | |
705 | + /* | |
706 | + * Pretend that sysrq was never pressed at all. This | |
707 | + * is needed to properly handle KGDB which will try | |
708 | + * to release all keys after exiting debugger. If we | |
709 | + * do not clear key bit it KGDB will end up sending | |
710 | + * release events for Alt and SysRq, potentially | |
711 | + * triggering print screen function. | |
712 | + */ | |
713 | + if (sysrq->active) | |
714 | + clear_bit(KEY_SYSRQ, sysrq->handle.dev->key); | |
636 | 715 | |
637 | - sysrq->alt = KEY_RESERVED; | |
716 | + break; | |
638 | 717 | |
639 | - } else if (value != 2) { | |
640 | - sysrq->alt = code; | |
641 | - sysrq->need_reinject = false; | |
642 | - } | |
643 | - break; | |
718 | + default: | |
719 | + if (sysrq->active && value && value != 2) { | |
720 | + sysrq->need_reinject = false; | |
721 | + __handle_sysrq(sysrq_xlate[code], true); | |
722 | + } | |
723 | + break; | |
724 | + } | |
644 | 725 | |
645 | - case KEY_SYSRQ: | |
646 | - if (value == 1 && sysrq->alt != KEY_RESERVED) { | |
647 | - sysrq->active = true; | |
648 | - sysrq->alt_use = sysrq->alt; | |
649 | - /* | |
650 | - * If nothing else will be pressed we'll need | |
651 | - * to re-inject Alt-SysRq keysroke. | |
652 | - */ | |
653 | - sysrq->need_reinject = true; | |
654 | - } | |
726 | + suppress = sysrq->active; | |
655 | 727 | |
656 | - /* | |
657 | - * Pretend that sysrq was never pressed at all. This | |
658 | - * is needed to properly handle KGDB which will try | |
659 | - * to release all keys after exiting debugger. If we | |
660 | - * do not clear key bit it KGDB will end up sending | |
661 | - * release events for Alt and SysRq, potentially | |
662 | - * triggering print screen function. | |
663 | - */ | |
664 | - if (sysrq->active) | |
665 | - clear_bit(KEY_SYSRQ, handle->dev->key); | |
728 | + if (!sysrq->active) { | |
666 | 729 | |
667 | - break; | |
730 | + /* | |
731 | + * See if reset sequence has changed since the last time. | |
732 | + */ | |
733 | + if (sysrq->reset_seq_version != sysrq_reset_seq_version) | |
734 | + sysrq_parse_reset_sequence(sysrq); | |
668 | 735 | |
669 | - default: | |
670 | - if (sysrq->active && value && value != 2) { | |
671 | - sysrq->need_reinject = false; | |
672 | - __handle_sysrq(sysrq_xlate[code], true); | |
673 | - } | |
674 | - break; | |
736 | + /* | |
737 | + * If we are not suppressing key presses keep track of | |
738 | + * keyboard state so we can release keys that have been | |
739 | + * pressed before entering SysRq mode. | |
740 | + */ | |
741 | + if (value) | |
742 | + set_bit(code, sysrq->key_down); | |
743 | + else | |
744 | + clear_bit(code, sysrq->key_down); | |
745 | + | |
746 | + if (was_active) | |
747 | + schedule_work(&sysrq->reinject_work); | |
748 | + | |
749 | + if (sysrq_detect_reset_sequence(sysrq, code, value)) { | |
750 | + /* Force emergency reboot */ | |
751 | + __handle_sysrq(sysrq_xlate[KEY_B], false); | |
675 | 752 | } |
676 | 753 | |
677 | - suppress = sysrq->active; | |
754 | + } else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) { | |
755 | + /* | |
756 | + * Pass on release events for keys that was pressed before | |
757 | + * entering SysRq mode. | |
758 | + */ | |
759 | + suppress = false; | |
760 | + } | |
678 | 761 | |
679 | - if (!sysrq->active) { | |
680 | - /* | |
681 | - * If we are not suppressing key presses keep track of | |
682 | - * keyboard state so we can release keys that have been | |
683 | - * pressed before entering SysRq mode. | |
684 | - */ | |
685 | - if (value) | |
686 | - set_bit(code, sysrq->key_down); | |
687 | - else | |
688 | - clear_bit(code, sysrq->key_down); | |
762 | + return suppress; | |
763 | +} | |
689 | 764 | |
690 | - if (was_active) | |
691 | - schedule_work(&sysrq->reinject_work); | |
765 | +static bool sysrq_filter(struct input_handle *handle, | |
766 | + unsigned int type, unsigned int code, int value) | |
767 | +{ | |
768 | + struct sysrq_state *sysrq = handle->private; | |
769 | + bool suppress; | |
692 | 770 | |
693 | - } else if (value == 0 && | |
694 | - test_and_clear_bit(code, sysrq->key_down)) { | |
695 | - /* | |
696 | - * Pass on release events for keys that was pressed before | |
697 | - * entering SysRq mode. | |
698 | - */ | |
699 | - suppress = false; | |
700 | - } | |
771 | + /* | |
772 | + * Do not filter anything if we are in the process of re-injecting | |
773 | + * Alt+SysRq combination. | |
774 | + */ | |
775 | + if (sysrq->reinjecting) | |
776 | + return false; | |
777 | + | |
778 | + switch (type) { | |
779 | + | |
780 | + case EV_SYN: | |
781 | + suppress = false; | |
701 | 782 | break; |
702 | 783 | |
784 | + case EV_KEY: | |
785 | + suppress = sysrq_handle_keypress(sysrq, code, value); | |
786 | + break; | |
787 | + | |
703 | 788 | default: |
704 | 789 | suppress = sysrq->active; |
705 | 790 | break; |
706 | 791 | |
707 | 792 | |
... | ... | @@ -785,8 +870,21 @@ |
785 | 870 | |
786 | 871 | static inline void sysrq_register_handler(void) |
787 | 872 | { |
873 | + extern unsigned short platform_sysrq_reset_seq[] __weak; | |
874 | + unsigned short key; | |
788 | 875 | int error; |
876 | + int i; | |
789 | 877 | |
878 | + if (platform_sysrq_reset_seq) { | |
879 | + for (i = 0; i < ARRAY_SIZE(sysrq_reset_seq); i++) { | |
880 | + key = platform_sysrq_reset_seq[i]; | |
881 | + if (key == KEY_RESERVED || key > KEY_MAX) | |
882 | + break; | |
883 | + | |
884 | + sysrq_reset_seq[sysrq_reset_seq_len++] = key; | |
885 | + } | |
886 | + } | |
887 | + | |
790 | 888 | error = input_register_handler(&sysrq_handler); |
791 | 889 | if (error) |
792 | 890 | pr_err("Failed to register input handler, error %d", error); |
... | ... | @@ -801,6 +899,36 @@ |
801 | 899 | sysrq_handler_registered = false; |
802 | 900 | } |
803 | 901 | } |
902 | + | |
903 | +static int sysrq_reset_seq_param_set(const char *buffer, | |
904 | + const struct kernel_param *kp) | |
905 | +{ | |
906 | + unsigned long val; | |
907 | + int error; | |
908 | + | |
909 | + error = strict_strtoul(buffer, 0, &val); | |
910 | + if (error < 0) | |
911 | + return error; | |
912 | + | |
913 | + if (val > KEY_MAX) | |
914 | + return -EINVAL; | |
915 | + | |
916 | + *((unsigned short *)kp->arg) = val; | |
917 | + sysrq_reset_seq_version++; | |
918 | + | |
919 | + return 0; | |
920 | +} | |
921 | + | |
922 | +static struct kernel_param_ops param_ops_sysrq_reset_seq = { | |
923 | + .get = param_get_ushort, | |
924 | + .set = sysrq_reset_seq_param_set, | |
925 | +}; | |
926 | + | |
927 | +#define param_check_sysrq_reset_seq(name, p) \ | |
928 | + __param_check(name, p, unsigned short) | |
929 | + | |
930 | +module_param_array_named(reset_seq, sysrq_reset_seq, sysrq_reset_seq, | |
931 | + &sysrq_reset_seq_len, 0644); | |
804 | 932 | |
805 | 933 | #else |
806 | 934 |